最近的一個項目使用的是spring mvc,權限框架使用的是shiro.html
不過有一個問題一直困擾着我,如今的session究竟是誰的session,是servlet的仍是shiro的.web
因而我把spring controller參數裏面的HttpServletRequest對象和HttpSession對象打印了出來spring
這兩個對象打印的結果是org.apache.shiro.web.servlet.ShiroHttpServletRequest和org.apache.shiro.web.servlet.ShiroHttpSessionapache
在不使用shiro時這些對象應該均爲tomcat所實現的類,這說明在shiro執行filter時將request對象包裝成了shiro實現的類.tomcat
那麼shiro是在何時將request對象包裝了的呢?安全
先看一下shiro的攔截器類圖:session
ShiroFilter 是整個 Shiro 的入口點,用於攔截須要安全控制的請求進行處理。ShiroFilter 繼承自AbstractShiroFilter,而AbstractShiroFilter繼承自OncePerRequestFiltermvc
先看一下OncePerRequestFilter的源碼 :app
public abstract class OncePerRequestFilter extends NameableFilter { // 已過濾屬性的後綴名 public static final String ALREADY_FILTERED_SUFFIX = ".FILTERED"; // 是否開啓過濾功能 private boolean enabled = true; public boolean isEnabled() { return enabled; } public void setEnabled(boolean enabled) { this.enabled = enabled; } public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException { // 獲取 Filter 已過濾的屬性名 String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName(); // 判斷是否已過濾 if (request.getAttribute(alreadyFilteredAttributeName) != null) { // 若已過濾,則進入 FilterChain 中下一個 Filter filterChain.doFilter(request, response); } else { // 若未過濾,則判斷是否未開啓過濾功能(其中 shouldNotFilter 方法將被廢棄,由 isEnabled 方法取代) if (!isEnabled(request, response) || shouldNotFilter(request)) { // 若未開啓,則進入 FilterChain 中下一個 Filter filterChain.doFilter(request, response); } else { // 若已開啓,則將已過濾屬性設置爲 true(只要保證 Request 中有這個屬性便可) request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE); try { // 在子類中執行具體的過濾操做 doFilterInternal(request, response, filterChain); } finally { // 當前 Filter 執行結束需移除 Request 中的已過濾屬性 request.removeAttribute(alreadyFilteredAttributeName); } } } } protected String getAlreadyFilteredAttributeName() { String name = getName(); if (name == null) { name = getClass().getName(); } return name + ALREADY_FILTERED_SUFFIX; } protected boolean isEnabled(ServletRequest request, ServletResponse response) throws ServletException, IOException { return isEnabled(); } protected boolean shouldNotFilter(ServletRequest request) throws ServletException { return false; } protected abstract void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException; }
咱們看到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.