spring mvc下shiro的session,request等问题 HttpShiroRequest 接管session
最近的一个项目使用的是spring mvc,权限框架使用的是shiro.
不过有一个问题一直困扰着我,现在的session到底是谁的session,是servlet的还是shiro的.
于是我把spring controller参数里面的HttpServletRequest对象和HttpSession对象打印了出来
这两个对象打印的结果是org.apache.shiro.web.servlet.ShiroHttpServletRequest和org.apache.shiro.web.servlet.ShiroHttpSession
在不使用shiro时这些对象应该均为tomcat所实现的类,这说明在shiro执行filter时将request对象包装成了shiro实现的类.
那么shiro是在什么时候将request对象包装了的呢?
prepareServletRequest和prepareServletResponse都是对上层传入的参数ServletRequest和ServletResponse进行再包装。
prepareServletRequest获取SecurityManager和sessionManager的实例
protected boolean isHttpSessions() {
return getSecurityManager().isHttpSessionMode();
}
我们看到OncePerRequestFilter里面实现了Servlet规范的doFilter(),并且将该方法声明为final,可以看出shiro不允许其子类再复写该方法.
但OncePerRequestFilter并没有实现 doFilterInternal(request, response, filterChain),这说明该方法是需要在子类中实现的.
再来看一下OncePerRequestFilter的子类AbstractShiroFilter:
protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain) throws ServletException, IOException { Throwable t = null; try { // 返回被 Shiro 包装过的 Request 与 Response 对象 final ServletRequest request = prepareServletRequest(servletRequest, servletResponse, chain); final ServletResponse response = prepareServletResponse(request, servletResponse, chain); // 创建 Shiro 的 Subject 对象 final Subject subject = createSubject(request, response); // 使用异步的方式执行相关操作 subject.execute(new Callable() { public Object call() throws Exception { // 更新 Session 的最后访问时间 updateSessionLastAccessTime(request, response); // 执行 Shiro 的 Filter Chain executeChain(request, response, chain); return null; } }); } catch (ExecutionException ex) { t = ex.getCause(); } catch (Throwable throwable) { t = throwable; } if (t != null) { if (t instanceof ServletException) { throw (ServletException) t; } if (t instanceof IOException) { throw (IOException) t; } throw new ServletException(t); } }
果然,在子类AbstractShiroFilter中实现了doFilterInternal()方法.
其中用于包装request的函数:
protected ServletRequest prepareServletRequest(ServletRequest request, ServletResponse response, FilterChain chain) { ServletRequest toUse = request; if(request instanceof HttpServletRequest) { HttpServletRequest http = (HttpServletRequest)request; toUse = this.wrapServletRequest(http); } return toUse; }
protected ServletRequest wrapServletRequest(HttpServletRequest orig) { return new ShiroHttpServletRequest(orig, this.getServletContext(), this.isHttpSessions()); }
现在终于知道shiro是怎么把request对象包装成ShiroHttpServletRequest类型的了.
一开始session的困惑也能解开了:
因为session是通过request获取的,所以先看一下ShiroHttpServletRequest获取session的源码:
public HttpSession getSession(boolean create) { HttpSession httpSession; if(this.isHttpSessions()) { httpSession = super.getSession(false); if(httpSession == null && create) { if(!WebUtils._isSessionCreationEnabled(this)) { throw this.newNoSessionCreationException(); } httpSession = super.getSession(create); } } else { if(this.session == null) { boolean existing = this.getSubject().getSession(false) != null; Session shiroSession = this.getSubject().getSession(create); if(shiroSession != null) { this.session = new ShiroHttpSession(shiroSession, this, this.servletContext); if(!existing) { this.setAttribute(REFERENCED_SESSION_IS_NEW, Boolean.TRUE); } } } httpSession = this.session; } return httpSession; }
这里主要讲的是:
如果this.isHttpSessions()返回true,则返回父类HttpServletRequestWrapper的
也就是servelet规范的session,否则返回ShiroHttpSession对象.
那么this.isHttpSessions()是什么呢?
protected boolean httpSessions = true;
public boolean isHttpSessions() { return this.httpSessions; }
this.isHttpSessions()返回ShiroHttpServletRequest对象的一个属性值,默认是true.
ShiroHttpServletRequest的构造函数是这样的:
public ShiroHttpServletRequest(HttpServletRequest wrapped, ServletContext servletContext, boolean httpSessions) { super(wrapped); this.servletContext = servletContext; this.httpSessions = httpSessions; }
这说明在ShiroHttpServletRequest对象生成之初就必须指定httpSessions的值.
再回到刚才shiro包装request的地方.
protected ServletRequest wrapServletRequest(HttpServletRequest orig) { return new ShiroHttpServletRequest(orig, this.getServletContext(), this.isHttpSessions()); }
protected boolean isHttpSessions() { return this.getSecurityManager().isHttpSessionMode(); }
这里的this.isHttpSessions()取决于this.getSecurityManager().isHttpSessionMode()的值.
我们项目里SecurityManager设置为DefaultWebSecurityManager.其中isHttpSessionMode()方法为:
public boolean isHttpSessionMode() { SessionManager sessionManager = this.getSessionManager(); return sessionManager instanceof WebSessionManager && ((WebSessionManager)sessionManager).isServletContainerSessions(); }
我们这个项目使用的sessionManager是DefaultWebSessionManager,DefaultWebSessionManager实现了sessionManager 接口.
但是DefaultWebSessionManager中该方法返回的是false.
public boolean isServletContainerSessions() { return false; }
所以最终session得到的是ShiroHttpSession.