一句话总结:会话域Context一路收集principals, authenticated, host, session(readSession()返回), sessionEnabled, request, response, securityManager ; 最终被存入到了返回的这个Subject中
1.第一步:ShiroFilterFactoryBean的初始化和创建
(1)实现BeanPostProcessor接口:具体看源码postProcessBeforeInitialization
实现的功能:将在Spring中注册的Filter并入到ShiroFilterFactoryBean的filters中,
目标:保证不遗漏Filter
(2)实现FactoryBean
实现的功能: 返回AbstractShiroFilter实例,本质是个过滤器(extends OncePerRequestFilter ),所以应用必须运行在servlet容器中
-->确保用户配置了SecurityManager
-->createFilterChainManager():
-->获取默认过滤器,为默认加载的Filter,应用上全局配置属性(applyGlobalPropertiesIfNecessary)
public enum DefaultFilter {
anon(AnonymousFilter.class),
authc(FormAuthenticationFilter.class),
authcBasic(BasicHttpAuthenticationFilter.class),
logout(LogoutFilter.class),
noSessionCreation(NoSessionCreationFilter.class),
perms(PermissionsAuthorizationFilter.class),
port(PortFilter.class),
rest(HttpMethodPermissionFilter.class),
roles(RolesAuthorizationFilter.class),
ssl(SslFilter.class),
user(UserFilter.class);
....
}
-->处理用户配置的ShiroFilterFactoryBean.filterChainDefinitions属性,添加到DefaultFilterChainManager对象的filterChains属性中
-->构造SpringShiroFilter实例,由Spring管理,具体看源码createInstance();
2.第二步:处理1个http请求原理
(1)1个请求1个createSubject原理:由于ShiroFilterFactoryBean本质是个AbstractShiroFilter过滤器,所以每次请求都会执行doFilterInternal里面的createSubject方法。
-->createSubject逻辑比较复杂,单独梳理。
(2)最终被并入到Servlet的FilterChain中的Filter实例为ShiroFilterFactoryBean.SpringShiroFilter类型
(3)doFilterInternal中的核心处理逻辑:executeChain
subject.execute(new Callable() {
public Object call() throws Exception {
AbstractShiroFilter.this.updateSessionLastAccessTime(request, response);
AbstractShiroFilter.this.executeChain(request, response, chain);
return null;
}
});
-->根据请求的request找到requestURI ,比如 以 http://xxx/myapp/my/test.jsp举例的话;这里返回的requestURI, 其值为 /my/test.jsp
-->筛选出与当前请求链接匹配的FilterChain:
DefaultFilterChainManager类中Map
-->一旦匹配, 则对Servlet的FilterChain进行代理。保证Shiro自定义的Filter执行完毕之后,再执行Servlet的Filter。
-->总结:
Shiro直接将整个FilterChain代理,先执行完我的FilterChain,才考虑Servlet的。
Shiro 对Servlet 容器的FilterChain 进行了代理,即ShiroFilter 在继续Servlet 容器的Filter链的执行之前,
通过ProxiedFilterChain`对Servlet 容器的FilterChain 进行了代理;即先走Shiro 自己的Filter 体系,
然后才会委托给Servlet 容器的FilterChain 进行Servlet 容器级别的Filter链执行; 《跟开涛学Shiro》
每次请求的到来,Shiro都会从我们配置到org.apache.shiro.spring.web.ShiroFilterFactoryBean的filterChainDefinitions中
挑选一个匹配过滤链(多个匹配也只会选择第一个匹配的),Shiro会执行这个链条,最后采取执行其他Servlet Filter
-->
3.第三步:shiro的扩展
-->都是向FilterChain中插入自定义Filter; 玩的都是过滤器,简单好用。。。。
-->扩展CachingSessionDAO,实现session的自定义存取
-->AuthenticationListener
-->LogoutAware
-->自定义SimpleSession,新增一些属性,比如在线状态等
-->自定义SessionFactory,初始化自定义的SimpleSession
4.补充:createSubject逻辑:
-->源码入口:
// AbstractShiroFilter.createSubject
protected WebSubject createSubject(ServletRequest request, ServletResponse response) {
// securityManager是shiro强制要求用户必须自己配置的。
// 在Web环境下,其为DefaultWebSecurityManager类型。这一点随便找个spring-shiro的配置文件就能看到了。
return new WebSubject.Builder(getSecurityManager(), request, response).buildWebSubject();
}
-->WebSubject.Builder构造函数:
public Builder(SecurityManager securityManager, ServletRequest request, ServletResponse response) {
// 调用Subject.Builder的构造函数
super(securityManager);
if (request == null) {
throw new IllegalArgumentException("ServletRequest argument cannot be null.");
}
if (response == null) {
throw new IllegalArgumentException("ServletResponse argument cannot be null.");
}
// 让SubjectContext会话域附加上当前请求request; 贯穿整个执行过程。
setRequest(request);
// 让SubjectContext会话域附加上当前响应response; 贯穿整个执行过程。
setResponse(response);
}
// Subject.Builder 构造函数
public Builder(SecurityManager securityManager) {
if (securityManager == null) {
throw new NullPointerException("SecurityManager method argument cannot be null.");
}
this.securityManager = securityManager;
// 构建一个SubjectContext会话域。
this.subjectContext = newSubjectContextInstance();
if (this.subjectContext == null) {
throw new IllegalStateException("Subject instance returned from 'newSubjectContextInstance' " +
"cannot be null.");
}
// 让会话域带着securityManager贯穿整个执行过程。
this.subjectContext.setSecurityManager(securityManager);
}
-->以上是初始化上下文,然后哒哒哒。。快马加鞭。,我们直接跳到WebSubject.Builder.buildWebSubject:
public Subject buildSubject() {
// 委托给了SecurityManager实例;
// 这里的securityManager实际类型为DefaultWebSecurityManager类型
// 而subjectContext的时机类型为DefaultWebSubjectContext, 而且按照之前的跟踪,
//我们知道该Context中已经被填入了当前请求的request,response以及securityManager实例
return this.securityManager.createSubject(this.subjectContext);
}
总之就是把创建subject的操作直接委托给了SecurityManager实例创建subject(携带subjectContext,这个上下文很重要)
-->
会话域Context一路收集来的principals, authenticated, host, session, sessionEnabled, request, response, securityManager ; 最终被存入到了返回的这个Subject中
--> 核心:DefaultSecurityManager.createSubject:
public Subject createSubject(SubjectContext subjectContext) {
//create a copy so we don't modify the argument's backing map:
// copy方法被子类DefaultWebSecurityManager重载; 返回一个DefaultWebSubjectContext实例
SubjectContext context = copy(subjectContext);
//ensure that the context has a SecurityManager instance, and if not, add one:
// 子类DefaultWebSecurityManager未进行重载,
// 此方法确保会话域持有一个SecurityManager来贯穿整个执行流程。
context = ensureSecurityManager(context);
//Resolve an associated Session (usually based on a referenced session ID), and place it in the context before
//sending to the SubjectFactory. The SubjectFactory should not need to know how to acquire sessions as the
//process is often environment specific - better to shield the SF from these details:
// 向会话域中存入一个Session实例;
// 此操作可能失败, 届时会构建一个缺少Session的会话域
// 注意这里的构建Session出错是被允许的, 所以异常是以Debug的方式输出的.
// Session的维护是交给了专门的SessionManager来负责
// 注意这里用的是SessionKey类型的Key,而不是简单的string类型的sessionId
// 因为Session我们操作的比较频繁,所以下文会进行详解
context = resolveSession(context);
//Similarly, the SubjectFactory should not require any concept of RememberMe - translate that here first
//if possible before handing off to the SubjectFactory:
// 这一步会向会话域中插入Principal; 此操作也有可能失败, 即最终会话域context中缺少Principal信息
// rememberMe的功能也是交给了专门的RememberMeManager
// 而且默认的RememberMe功能是通过Cookie来完成的, 所以默认的实现是CookieRememberMeManager; 而且cookie的默认名称是rememberMe
// 而且Shiro有自己专门的Cookie接口,而唯一的实现则是SimpleCookie
context = resolvePrincipals(context);
// 看过Spring源码的都知道这命名意味着什么, 真正干活的来了。
// 创建Subject的工作又被委派给了专门的SubjectFactory, 七拐八绕啊。
// SubjectFactory接口的默认实现为DefaultWebSubjectFactory
// 观察其对createSubject方法的实现正式将会话域context这一路收集来的信息汇总生成一个WebDelegatingSubject实例(又增加一个中间层)。
Subject subject = doCreateSubject(context);
//save this subject for future reference if necessary:
//(this is needed here in case rememberMe principals were resolved and they need to be stored in the
//session, so we don't constantly rehydrate the rememberMe PrincipalCollection on every operation).
//Added in 1.2:
// 专门的SubjectDAO接口负责对该subject进行保存操作
// SubjectDAO接口的默认实现类为DefaultSubjectDAO
save(subject);
return subject;
}
public class DefaultWebSubjectFactory extends DefaultSubjectFactory {
public DefaultWebSubjectFactory() {
}
public Subject createSubject(SubjectContext context) {
if (!(context instanceof WebSubjectContext)) {
return super.createSubject(context);
} else {
WebSubjectContext wsc = (WebSubjectContext)context;
SecurityManager securityManager = wsc.resolveSecurityManager();
Session session = wsc.resolveSession();
boolean sessionEnabled = wsc.isSessionCreationEnabled();
PrincipalCollection principals = wsc.resolvePrincipals();
boolean authenticated = wsc.resolveAuthenticated();
String host = wsc.resolveHost();
ServletRequest request = wsc.resolveServletRequest();
ServletResponse response = wsc.resolveServletResponse();
return new WebDelegatingSubject(principals, authenticated, host, session, sessionEnabled, request, response, securityManager);
}
}
...
}
-->context = resolveSession(context)分析
最终是执行:DefaultSessionManager类中的一段逻辑:retrieveSessionFromDataSource:
看到这个方法的代码我们就知道是通过外部的sessionDao去读了,而这个Dao恰好是我们经常扩展的Dao,比如改成从redis存取数据
protected Session retrieveSession(SessionKey sessionKey) throws UnknownSessionException {
Serializable sessionId = this.getSessionId(sessionKey);
if (sessionId == null) {
log.debug("Unable to resolve session ID from SessionKey [{}]. Returning null to indicate a session could not be found.", sessionKey);
return null;
} else {
Session s = this.retrieveSessionFromDataSource(sessionId);
if (s == null) {
String msg = "Could not find session with ID [" + sessionId + "]";
throw new UnknownSessionException(msg);
} else {
return s;
}
}
}
protected Session retrieveSessionFromDataSource(Serializable sessionId) throws UnknownSessionException {
return this.sessionDAO.readSession(sessionId);
}
-->context = this.resolvePrincipals(context);分析:
从RememberMeManager中获取凭证