shiro架构分析
全局工具类SecurityUtils
在org.apache.shiro
包下的一个类。SecurityUtils类中提供了设置主体,获取主体,设置安全管理器,获取安全管理器的方法
主体subject
DelegatingSubject
类中重写了login()
方法:
public void login(AuthenticationToken token) throws AuthenticationException {
this.clearRunAsIdentitiesInternal();
Subject subject = this.securityManager.login(this, token);
........
}
该方法调用了SecurityManager中的login()
方法
关于安全管理器SecurityManager
在org.apache.shiro.mgt
包下(mgt即management),根据文档的说法,SecurityManager继承了Authenticator, Authorizer, and SessionManager,将他们分别要做的行为合并到同一个地方来执行,对于大部分shiro的使用场景,这样做简化了配置,而且比分开使用Authenticator, Authorizer, and SessionManager 的实例要更简便,一个个体只需要跟一个单独的SecurityManager产生关联
除了继承的三个接口以外,SecurityManager还提供了一些支持subject行为的方法。对于单一一个用户,subject需要执行authentication, authorization, and session operations,而SecurityManager对这三个功能都能掌控,而三个父接口与subject并没有什么联系,如果将subject相关的某些操作交给他们来做,无法确保Separation of Concerns,关注点分离
Separation of Concerns,关注点分离,将一个程序分割成不同模块,一个模块只关心一个点,类似于职责分离
从抽象类的继承中我们可以看出每一次继承都完善了securitymanager的功能,CachingSecurityManager
增添了缓存的管理CacheManager
,RealmSecurityManager
中增添了realm
,AuthenticatingSecurityManager
和AuthorizingSecurityManager
分别增添了Authenticator
和Authorizer
。所以安全管理器用来集成realm,认证器跟授权器
public interface SecurityManager extends Authenticator, Authorizer, SessionManager {
Subject login(Subject var1, AuthenticationToken var2) throws AuthenticationException;
void logout(Subject var1);
Subject createSubject(SubjectContext var1);
}
SecurityManager中提供了login()
方法以及createSubject()
方法,在SecurityUtils
中创建主体就是用这个方法
而login()
方法是在DefaultSecurityManager
中被实现的,DefaultSecurityManager
已经间接继承了AuthenticatingSecurityManager
类,所以在login()
方法中调用了authenticate()
方法进一步调用认证器authenticator
中的抽象方法doAuthenticate()
进行认证
DefaultSecurityManager
常用方法:
Realm
根据文档的说法,AuthenticatingRealm是顶层的Realm接口的抽象实现类,而且它只完善了authentication的支持,authorization的部分留给了子类来完善;AuthorizingRealm就继承了AuthenticatingRealm,增加了authorization的支持,所以一般会选择继承AuthorizingRealm来自定义realm,重写其中的doGetAuthorizationInfo()方法和doGetAuthenticationInfo()方法
认证器authenticator
Authenticator中提供了authenticate()方法用于认证:
package org.apache.shiro.authc;
public interface Authenticator {
AuthenticationInfo authenticate(AuthenticationToken var1) throws AuthenticationException;
}
同级目录下的AbstractAuthenticator是几乎所有Authenticator实现类的超类,它设置了authentication时一些普遍的工作。他把具体的事实上的authentication过程交给子类来完成,然后自己支持当登陆或登出成功或失败时向监听器通知,从而触发相应的处理逻辑。在大部分情况下,子类唯一需要做的事就是实现doAuthenticate()方法来对接收的token完善具体的认证流程
org.apache.shiro.authc.pam包下的ModularRealmAuthenticator
类中就继承了AbstractAuthenticator,实现了doAuthenticate()
:
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
this.assertRealmsConfigured();
Collection<Realm> realms = this.getRealms();
return realms.size() == 1 ? this.doSingleRealmAuthentication((Realm)realms.iterator().next(), authenticationToken) : this.doMultiRealmAuthentication(realms, authenticationToken);
}
可以看出ModularRealmAuthenticator将账户查找交给Realm来完成。同级目录下还有AuthenticationStrategy接口,认证策略
FirstSuccessfulStrategy
:只要一个 Realm 验证成功即可,只返回第 一个 Realm 身份验证成功的认证信息,其他的忽略
AtLeastOneSuccessfulStrategy
:只要有一个Realm验证成功即可,将返回所有Realm身份验证成功的认证信 息
AllSuccessfulStrategy
:所有Realm验证成功才算成功,且返回所有 Realm身份验证成功的认证信息
ModularRealmAuthenticator
默认是AtLeastOneSuccessfulStrategy
策略
在RealmSecurityManager
类:
public void setRealms(Collection<Realm> realms) {
if (realms == null) {
throw new IllegalArgumentException("Realms collection argument cannot be null.");
} else if (realms.isEmpty()) {
throw new IllegalArgumentException("Realms collection argument cannot be empty.");
} else {
this.realms = realms;
this.afterRealmsSet();
}
}
在设置完realm后会自己调用afterRealmsSet()
方法,在AuthenticatingSecurityManager
中会通过这个方法将realm同时设为认证器authenticator
的realm:
protected void afterRealmsSet() {
super.afterRealmsSet();
if (this.authenticator instanceof ModularRealmAuthenticator) {
((ModularRealmAuthenticator)this.authenticator).setRealms(this.getRealms());
}
}
也就是说,在DefaultSecurityManager类对象中调用setRealm()方法,认证器的realm也会保持一致
认证信息
过滤器
在org.apache.shiro.web.filter
包下,PathConfigProcessor
接口为每个url请求处理配置条目
PathMatchingFilter
抽象类是所有只会处理指定路径,让其他路径直接通过的拦截器的基类
AccessControlFilter
抽象类继承了PathMatchingFilter,是所有会对资源访问进行控制的拦截器的超类。该类中有一个字段DEFAULT_LOGIN_URL,值为"/login.jsp",将"/login.jsp"默认设为用于登录的URL。getSubject() 方法可用于获取该请求对应的subject;isAccessAllowed() 方法,当请求被允许正常通过拦截器时返回true,否则返回false,这个请求应当被onAccessDenied处理;onPreHandle() 当isAccessAllowed方法通过时返回true,否则返回onAccessDenied方法的返回值
在org.apache.shiro.web.filter.authc
包下。这些过滤器有的是javax.servlet包下的Filter接口的实现类,用来对用户访问进行控制;有的则是可以直接执行认证操作的
抽象类AuthenticationFilter是所有要求当前用户进行认证的过滤器的基类,其中实现了isAccessAllowed()方法:当主体已被认证且身份信息不为空时返回true
抽象类AuthenticatingFilter在AuthenticationFilter基础上已有能力可以自己实现一个认证操作
较为常用的BasicHttpAuthenticationFilter。getPrincipalsAndCredentials() 方法用于从请求头中获取用户名和密码
过滤器工厂
org.apache.shiro.spring.web包下的ShiroFilterFactoryBean。filterChainDefinitionMap中放的是要拦截的路径以及相对应的过滤器;filters中放的是过滤器以及过滤器对应的名称,这个名称用于在filterChainDefinitionMap中设置过滤器时使用;loginUrl是应用中用于登陆的Url,所有AccessControlFilter的子类都有这个字段;setSecurityManager,设置安全管理器
将这个类在shiro的配置类中配置即可实现过滤器的配置