Shiro在于Spring集成中,需要配置SecurityManager,Realm,ShiroFilterFactoryBean这三个类。在Web环境中SecurityManager一般配置DefaultWebSecurityManager,如果需要扩展或者定制一些额外的功能,可以配置DefaultWebSecurityManager的继承类;Realm需要先继承AuthorizingRealm抽象类再配置,如果有多个Realm的话,还需要配置ModularRealmAuthenticator的继承实现类;ShiroFilterFactoryBean主要是提供ShiroFilter,可以配置一些资源的拦截。下面对一些核心类进行一下总结。
该类继承了三个接口,还额外提供登录,退出和创建用户的功能。
/**
* 所有与安全有关的操作都会与SecurityManager交互
* 扩展了authenticator、authorizer和sessionmanager接口
*/
public interface SecurityManager extends Authenticator, Authorizer, SessionManager {
Subject login(Subject subject, AuthenticationToken authenticationToken) throws AuthenticationException;
void logout(Subject subject);
Subject createSubject(SubjectContext context);
}
/**
* 认证验证,登录校验
*
*/
public interface Authenticator {
/**
* AuthenticationToken 登录未验证的数据
* AuthenticationInfo 身份验证/登录过程相关的帐户信息。
*
*/
public AuthenticationInfo authenticate(AuthenticationToken authenticationToken)
throws AuthenticationException;
}
/**
* 用户授权,权限校验
*
*/
public interface Authorizer {
boolean[] isPermitted(PrincipalCollection subjectPrincipal, String... permissions);
boolean[] isPermitted(PrincipalCollection subjectPrincipal, List<Permission> permissions);
boolean isPermittedAll(PrincipalCollection subjectPrincipal, String... permissions);
boolean isPermittedAll(PrincipalCollection subjectPrincipal, Collection<Permission> permissions);
void checkPermissions(PrincipalCollection subjectPrincipal, String... permissions) throws AuthorizationException;
void checkPermissions(PrincipalCollection subjectPrincipal, Collection<Permission> permissions) throws AuthorizationException;
boolean hasRole(PrincipalCollection subjectPrincipal, String roleIdentifier);
boolean[] hasRoles(PrincipalCollection subjectPrincipal, List<String> roleIdentifiers);
boolean hasAllRoles(PrincipalCollection subjectPrincipal, Collection<String> roleIdentifiers);
void checkRole(PrincipalCollection subjectPrincipal, String roleIdentifier) throws AuthorizationException;
void checkRoles(PrincipalCollection subjectPrincipal, Collection<String> roleIdentifiers) throws AuthorizationException;
void checkRoles(PrincipalCollection subjectPrincipal, String... roleIdentifiers) throws AuthorizationException;
}
/**
* 会话管理
*/
public interface SessionManager {
/**
* 基于指定的上下文初始化数据启动一个新Session,Session通常交由SessionFactory创建
*
*/
Session start(SessionContext context);
/**
* 通过SessionKey查找Session
*
*/
Session getSession(SessionKey key) throws SessionException;
}
SecurityManager的Web部分源代码实现如下所示。从默认的构造器可以看到在创建SecurityManager的该实现时,会设置一系列默认的值,如ServletContainerSessionManager,CookieRememberMeManager等。而isHttpSessionMode方法判断是否是HttpSession,还是自己实现的Session。
public class DefaultWebSecurityManager extends DefaultSecurityManager implements WebSecurityManager {
public DefaultWebSecurityManager() {
super();
DefaultWebSessionStorageEvaluator webEvalutator = new DefaultWebSessionStorageEvaluator();
((DefaultSubjectDAO) this.subjectDAO).setSessionStorageEvaluator(webEvalutator);
this.sessionMode = HTTP_SESSION_MODE;
setSubjectFactory(new DefaultWebSubjectFactory());
setRememberMeManager(new CookieRememberMeManager());
setSessionManager(new ServletContainerSessionManager());
webEvalutator.setSessionManager(getSessionManager());
}
public boolean isHttpSessionMode() {
SessionManager sessionManager = getSessionManager();
return sessionManager instanceof WebSessionManager && ((WebSessionManager)sessionManager).isServletContainerSessions();
}
...
}
以下是SecurityManager中实现SessionManager接口的实现类,从中可以看到SecurityManager并没有实际处理SessionManager接口的方法,而是采用组合模式,将实际的SessionManager作为SecurityManager的成员变量,实际处理还是交由sessionManager来处理。而且在SessionManager初始化完默认的DefaultSessionManager后(在新继承的DefaultWebSecurityManager的类中,为ServletContainerSessionManager)后,如果SessionManager实现CacheManagerAware接口,则会将CacheManager也一同设置到SessionManager中。
public abstract class SessionsSecurityManager extends AuthorizingSecurityManager {
private SessionManager sessionManager;
public SessionsSecurityManager() {
super();
this.sessionManager = new DefaultSessionManager();
applyCacheManagerToSessionManager();
}
protected void applyCacheManagerToSessionManager() {
if (this.sessionManager instanceof CacheManagerAware) {
((CacheManagerAware) this.sessionManager).setCacheManager(getCacheManager());
}
}
public void setSessionManager(SessionManager sessionManager) {
this.sessionManager = sessionManager;
afterSessionManagerSet();
}
protected void afterSessionManagerSet() {
applyCacheManagerToSessionManager();
applyEventBusToSessionManager();
}
public SessionManager getSessionManager() {
return this.sessionManager;
}
public Session start(SessionContext context) throws AuthorizationException {
return this.sessionManager.start(context);
}
public Session getSession(SessionKey key) throws SessionException {
return this.sessionManager.getSession(key);
}
}
从SecurityManager的继承体系来看,每次的继承都会添加一个成员变量,并且对外公开的方法也是由该成员来处理。所以现在来看,SecurityManager是通过继承体系和组合的模式,来充实它的实际功能,并且将Shiro的各个组件都联系到了一起。SecurityManager是线程安全且真个应用只需要一个即可,因此Shiro提供了SecurityUtils让我们绑定它为全局的,方便后续操作。
Realm:域,Shiro从从Realm获取安全数据(如用户、角色、权限),就是说 SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成 DataSource,即安全数据源。 通常由程序实现AuthorizingRealm类,如果有多个实现,还需要重写ModularRealmAuthenticator的doAuthenticate的方法,来指定Realm对应处理的AuthenticationToken。另外AuthorizingRealm提供设置缓存,加密和权限的相关功能。
public interface Realm {
/**
* 返回应用中Realm的唯一名字
*/
String getName();
/**
* 多Realm中,该Realm是否匹配AuthenticationToken
*/
boolean supports(AuthenticationToken token);
/**
* 依据未认证的AuthenticationToken,返回认证后的AuthenticationInfo
*/
AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException;
}
Authenticator的功能是验证用户帐号,是Shiro API中身份认证核心的入口点。如果验证成功,将返回 AuthenticationInfo验证信息;此信息中包含了身份及凭证;如果验证失败将抛出相应的 AuthenticationException实现异常。它的默认实现类是ModularRealmAuthenticator,可以从这个类中看到校验的整个流程,而且还提供了AuthenticationListener来监听认证的过程(主要有登录成功事件,登录失败事件和退出事件)。
public class ModularRealmAuthenticator extends AbstractAuthenticator {
private static final Logger log = LoggerFactory.getLogger(ModularRealmAuthenticator.class);
private Collection<Realm> realms;
private AuthenticationStrategy authenticationStrategy;
public ModularRealmAuthenticator() {
this.authenticationStrategy = new AtLeastOneSuccessfulStrategy();
}
public void setRealms(Collection<Realm> realms) {
this.realms = realms;
}
protected Collection<Realm> getRealms() {
return this.realms;
}
public AuthenticationStrategy getAuthenticationStrategy() {
return authenticationStrategy;
}
public void setAuthenticationStrategy(AuthenticationStrategy authenticationStrategy) {
this.authenticationStrategy = authenticationStrategy;
}
protected void assertRealmsConfigured() throws IllegalStateException {
Collection<Realm> realms = getRealms();
if (CollectionUtils.isEmpty(realms)) {
String msg = "Configuration error: No realms have been configured! One or more realms must be " +
"present to execute an authentication attempt.";
throw new IllegalStateException(msg);
}
}
/**
* 单Realm的校验,最后调用realm.getAuthenticationInfo方法来通过Realm校验正确性。
*/
protected AuthenticationInfo doSingleRealmAuthentication(Realm realm, AuthenticationToken token) {
if (!realm.supports(token)) {
String msg = "Realm [" + realm + "] does not support authentication token [" +
token + "]. Please ensure that the appropriate Realm implementation is " +
"configured correctly or that the realm accepts AuthenticationTokens of this type.";
throw new UnsupportedTokenException(msg);
}
AuthenticationInfo info = realm.getAuthenticationInfo(token);
if (info == null) {
String msg = "Realm [" + realm + "] was unable to find account data for the " +
"submitted AuthenticationToken [" + token + "].";
throw new UnknownAccountException(msg);
}
return info;
}
/**
* 多Realm的校验,还需要考虑认证的策略(全部成功,至少一个成功)
*/
protected AuthenticationInfo doMultiRealmAuthentication(Collection<Realm> realms, AuthenticationToken token) {
AuthenticationStrategy strategy = getAuthenticationStrategy();
AuthenticationInfo aggregate = strategy.beforeAllAttempts(realms, token);
if (log.isTraceEnabled()) {
log.trace("Iterating through {} realms for PAM authentication", realms.size());
}
for (Realm realm : realms) {
aggregate = strategy.beforeAttempt(realm, token, aggregate);
if (realm.supports(token)) {
log.trace("Attempting to authenticate token [{}] using realm [{}]", token, realm);
AuthenticationInfo info = null;
Throwable t = null;
try {
info = realm.getAuthenticationInfo(token);
} catch (Throwable throwable) {
t = throwable;
if (log.isDebugEnabled()) {
String msg = "Realm [" + realm + "] threw an exception during a multi-realm authentication attempt:";
log.debug(msg, t);
}
}
aggregate = strategy.afterAttempt(realm, token, info, aggregate, t);
} else {
log.debug("Realm [{}] does not support token {}. Skipping realm.", realm, token);
}
}
aggregate = strategy.afterAllAttempts(token, aggregate);
return aggregate;
}
/**
* 多Realm的校验,还需要考虑认证的策略(全部成功,至少一个成功)
*/
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
assertRealmsConfigured();
Collection<Realm> realms = getRealms();
if (realms.size() == 1) {
return doSingleRealmAuthentication(realms.iterator().next(), authenticationToken);
} else {
return doMultiRealmAuthentication(realms, authenticationToken);
}
}
public void onLogout(PrincipalCollection principals) {
super.onLogout(principals);
Collection<Realm> realms = getRealms();
if (!CollectionUtils.isEmpty(realms)) {
for (Realm realm : realms) {
if (realm instanceof LogoutAware) {
((LogoutAware) realm).onLogout(principals);
}
}
}
}
}
SecurityManager提供了如下接口,另外用于 Web 环境的 WebSessionManager又提供了如下接口,判断是否是Servlet容器的Session,还是自己维护Session。SecurityManager管理着应用中所有Subject的会话的创建、维护、删除、失效、验证等工作。
public interface SessionManager {
/**
* 启动会话
*/
Session start(SessionContext context);
Session getSession(SessionKey key) throws SessionException;
}
public interface WebSecurityManager extends SecurityManager {
boolean isHttpSessionMode();
}
在web环境中,如果用户不主动退出是不知道会话是否过期的,因此需要定期的检测会话是否过期,Shiro 提供了会话验证调度器SessionValidationScheduler来做这件事情。SecurityManager的实现类中一般都实现了该接口。
Shiro提供了SessionManager的三个默认实现:
DefaultSessionManager:DefaultSecurityManager 使用的默认实现,用于JavaSE环境
ServletContainerSessionManager:DefaultWebSecurityManager使用的默认实现,用于 Web环境,其直接使用Servlet容器的会话;
DefaultWebSessionManager :用于Web环境的实现,可以代替ServletContainerSessionManager,自己维护着会话,直接废弃了 Servlet 容器的会话管理。
Session是用户访问应用时保持的连接关系,在多次交互中应用能够识别出当前访问的用户是谁,且可以在多次交互中保存一些数据。如访问一些网站时登录成功后,网站可以记住用户,且在退出之前都可以识别当前用户是谁。Shiro的会话支持不仅可以在普通的JavaSE应用中使用,也可以在JavaEE应用中使用,如web应用。且使用方式是一致的 。
public interface Session {
Serializable getId();
Date getStartTimestamp();
Date getLastAccessTime();
long getTimeout() throws InvalidSessionException;
void setTimeout(long maxIdleTimeInMillis) throws InvalidSessionException;
String getHost();
/**
* 如果是 JavaSE 应用需要自己定期调用 session.touch()去更新最后访问时间;
* 如果是 Web 应用,每次进入 ShiroFilter 都会自动调用 session.touch()来更新最后访问时间
*/
void touch() throws InvalidSessionException;
/**
* 当Subject.logout()时会自动调用 stop 方法来销毁会话
*/
void stop() throws InvalidSessionException;
Collection<Object> getAttributeKeys() throws InvalidSessionException;
Object getAttribute(Object key) throws InvalidSessionException;
void setAttribute(Object key, Object value) throws InvalidSessionException;
Object removeAttribute(Object key) throws InvalidSessionException;
}
Session提供了监听器SessionListener,用于监听会话创建、过期及停止事件,如果只想监听某一个事件,可以继承SessionListenerAdapter实现。
Shiro提 SessionDAO用于会话的CRUD操作,AbstractSessionDAO提供了 SessionDAO的基础实现,如生成会话 ID等;CachingSessionDAO提供了对开发者透明的会话缓存的功能,只需要设置相应的 CacheManager 即可;MemorySessionDAO直接在内存中进行会话维护;而EnterpriseCacheSessionDAO提供了缓存功能的会话维护,但是都是空方法,需要继承实现这些方法。
Shiro与Spring的整合中,很多对Shiro的功能扩展,都需要继承原来的类,再修改为默认的实现。比如在Web环境中可以自己实现Session管理,就需要在SecurityManager中调用setSessionManager()方法,修改默认的SessionManager。
还有在Shiro和Spring整合中碰到了一个问题UserRealm中注入IUserService,导致IUserService的AOP失效(会导致事务失效等),只是查明了原有,解决办法可以不用注入,改为SpringContextHolder.getBean(IUserService.class)
的方式。
原文:https://www.cnblogs.com/fzsyw/p/11443740.html