先看shiro过滤器的UML
已经有cookie保存sessionId的请求场景:
1.请求第一次通过shiro的过滤器 OncePerRequestFilter 时,会把request,response包装成ShiroHttpServletRequest和ShiroHttpServletResponse,并且在请求中设置一个属性标识,作用就是当这个请求再次经过过滤器 OncePerRequestFilter 时,不会再包装请求和响应了.
2.请求第一次通过 OncePerRequestFilter 时,会把请求分到 AbstractShiroFilter 过滤器中,看上面的UML图,然后在AbstractShiroFilter中,有下面代码:
final ServletRequest request = this.prepareServletRequest(servletRequest, servletResponse, chain);
final ServletResponse response = this.prepareServletResponse(request, servletResponse, chain);
Subject subject = this.createSubject(request, response);
subject.execute(new Callable() {
public Object call() throws Exception {
AbstractShiroFilter.this.updateSessionLastAccessTime(request, response);
AbstractShiroFilter.this.executeChain(request, response, chain);
return null;
}
});
上面的代码分析:
第一行是包装servlet规范的request变成ShiroHttpServletRequest
第二行是包装servlet规范的response变成ShiroHttpServletResponse
第三行是创建当前请求的subject(即当前shiro的subject),有sessionId就创建包含sessionId的subject,没有就创建没有包含sessionId的subject
剩下的就是当前的subject执行shiro配置的过滤器链,这些shiro链中的过滤器,都是继承OncePerRequestFilter 的,所以当前的请求会再次通过OncePerRequestFilter ,当这个请求再次通过OncePerRequestFilter 时,已经在请求中设置了一个属性标识,通过判断这个属性标识,不会再次包装请求和响应,即不会再次包装ShiroHttpServletRequest和ShiroHttpServletResponse
3.回归主题,当执行
Subject subject = this.createSubject(request, response);
这句代码跟踪发现,请求会到 DefaultWebSessionManager 类中,就是session的管理器
在这个类中方法
private Serializable getReferencedSessionId(ServletRequest request, ServletResponse response) {
String id = this.getSessionIdCookieValue(request, response);
if (id != null) {
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, "cookie");
}
上面代码就是从请求过来的cookie中获取sessionId
shiro可以配置自己的cookie模版,把这个模版返回给浏览器,然后当请求过来时,根据这cookie模版的名字获取值sessionId
获取sessionId后,就在请求中设置属性,说请求中的sessionId,是从cookie中获取的,不是新创建的
请求再次设置如下两个属性,说是shiro请求的sessionId,和这个sessionId是有效的
if (id != null) {
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
}
4.从cookie中获取了sessionId后,shiro是怎么获取保存的session的?
注意:和session相关的操作,要找shiro中session管理器,因为session管理器是管理session创建,获取,删除的,所以一定要有这样的意识
在DefaultWebSessionManager 的父类中extends DefaultSessionManager
发现如下代码
Session s = this.retrieveSessionFromDataSource(sessionId);
protected Session retrieveSessionFromDataSource(Serializable sessionId) throws UnknownSessionException {
return this.sessionDAO.readSession(sessionId);
}
可知是找sessionDAO,根据sessionId获取session对象的
sessionDAO是session访问对象,将session保存在缓存中,而session所在的缓存可以是redis或其他
5.shiro框架会先使用sessionDAO根据sessionId获取session,后再调用sessionDAO的create(session)方法,再次保存到缓存中,即相当于更新了session的有效期,访问一次就更新一次
public interface SessionDAO {
Serializable create(Session var1);
Session readSession(Serializable var1) throws UnknownSessionException;
void update(Session var1) throws UnknownSessionException;
void delete(Session var1);
Collection<Session> getActiveSessions();
}
一开始访问应用,还没有创建session场景
1.一开始访问shiro框架时,没有cookie,没有sessionId
shiro框架也会看有没有cookie,有没有sessionId
注意:补充重要知识
在请求第一次通过 OncePerRequestFilter 时,会把请求分到 AbstractShiroFilter 过滤器中,
进入
final ServletRequest request = this.prepareServletRequest(servletRequest, servletResponse, chain);
final ServletResponse response = this.prepareServletResponse(request, servletResponse, chain);
Subject subject = this.createSubject(request, response);
subject.execute(new Callable() {
public Object call() throws Exception {
AbstractShiroFilter.this.updateSessionLastAccessTime(request, response);
AbstractShiroFilter.this.executeChain(request, response, chain);
return null;
}
});
上面代码除包装request,response和创建subject外,还更新session最新访问时间(如果有)
开始执行shiro中的过滤器链
AbstractShiroFilter.this.executeChain(request, response, chain);
上面这句代码是shiro过滤器链执行的开始,具体怎么做?看代码
protected void executeChain(ServletRequest request, ServletResponse response, FilterChain origChain) throws IOException, ServletException {
FilterChain chain = this.getExecutionChain(request, response, origChain);
chain.doFilter(request, response);
}
看this.getExecutionChain
protected FilterChain getExecutionChain(ServletRequest request, ServletResponse response, FilterChain origChain) {
FilterChain chain = origChain;
FilterChainResolver resolver = this.getFilterChainResolver();
if (resolver == null) {
log.debug("No FilterChainResolver configured. Returning original FilterChain.");
return origChain;
} else {
FilterChain resolved = resolver.getChain(request, response, origChain);
if (resolved != null) {
log.trace("Resolved a configured FilterChain for the current request.");
chain = resolved;
} else {
log.trace("No FilterChain configured for the current request. Using the default.");
}
return chain;
}
}
上面代码就是获取FilterChainResolver ,这个类在shiro框架启动时就把shiro配置的所有属于shiro的每个路径对应的过滤器链集中存放起来,再把tomcat的过滤器链保存shiro过滤器链resolved中,等执行完shiro的过滤器链后,再执行tomcat过滤器链
shiro的过滤器链是 ProxiedFilterChain
ProxiedFilterChain implements FilterChain
2.回归正题,当前请求通过当前请求路径对应的shiro过滤器链集中的一个链后
就到了servlet,即springmvc的dispatcherServlet
在接下来执行中
最终是javax.servlet.http.HttpServletRequestWrapper包装类中,调用ShiroHttpServletRequest中的
在通过shiro过滤器时,就包装了request为ShiroHttpServletRequest
public HttpSession getSession() {
return this.getSession(true);
}
Session shiroSession = this.getSubject().getSession(create);//由shiro的subject创建
this.session = new ShiroHttpSession(shiroSession, this, this.servletContext);
ShiroHttpSession 是继承 servlet规范的 HttpSession 的,这里将shiro的Session包装,
shiro最后调用SessionDAO保存session
这样传到controller层的时候,使用的就是shiro的Session