shiro將ServletHttpSession包裝成了ShiroHttpSession
最近在做Oauth SSO單點登入整合,目標有三個: A:各個業務系統在統一門戶登入,各業務系統不用再次登入,即SSO; B:原來各業務系統的登入入口暫時保留; C:解決同一個瀏覽器登入多個賬戶,session覆蓋的問題。
因為業務系統使用的是SpringMVC,並且使用了shiro安全框架。我的實現邏輯是,先執行OauthFilter,後執行Shiro認證。
但是在shiro中session設定的屬性,在OauthFilter中始終無法獲取,於是決定列印一下日誌。
在shiro中:
HttpSession session = req.getSession(); System.out.println("shiroFilter-session:"+session); session.setAttribute("CURRENT_USER"+accessToken, token.getUsername());
在OauthFilter中:
HttpSession session = request.getSession();
System.out.println("OauthFilter-session:"+session);
輸出日誌:
shiroFilter-session:[email protected]
OauthFilter-session:[email protected]
發現,這兩個session不是同一個Session,shiro將ServletHttpSession轉換成了ShiroHttpSession。 跟蹤一下原始碼:AbstractShiroFilter
protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain) throws ServletException, IOException { Throwable t = null; try { final ServletRequest request = prepareServletRequest(servletRequest, servletResponse, chain); final ServletResponse response = prepareServletResponse(request, servletResponse, chain); final Subject subject = createSubject(request, response); //noinspection unchecked subject.execute(new Callable() { public Object call() throws Exception { updateSessionLastAccessTime(request, response); 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; } //otherwise it's not one of the two exceptions expected by the filter method signature - wrap it in one: String msg = "Filtered request failed."; throw new ServletException(msg, t); } } @SuppressWarnings({"UnusedDeclaration"}) protected ServletRequest prepareServletRequest(ServletRequest request, ServletResponse response, FilterChain chain) { ServletRequest toUse = request; if (request instanceof HttpServletRequest) { HttpServletRequest http = (HttpServletRequest) request; toUse = wrapServletRequest(http); } return toUse; } protected ServletRequest wrapServletRequest(HttpServletRequest orig) { return new ShiroHttpServletRequest(orig, getServletContext(), isHttpSessions()); } protected boolean isHttpSessions() { return getSecurityManager().isHttpSessionMode(); }
現在終於知道shiro是怎麼把request物件包裝成ShiroHttpServletRequest型別的了.
一開始session的困惑也能解開了:
因為session是通過request獲取的,所以先看一下ShiroHttpServletRequest獲取session的原始碼:
public HttpSession getSession(boolean create) {
HttpSession httpSession;
if (isHttpSessions()) {
httpSession = super.getSession(false);
if (httpSession == null && create) {
//Shiro 1.2: assert that creation is enabled (SHIRO-266):
if (WebUtils._isSessionCreationEnabled(this)) {
httpSession = super.getSession(create);
} else {
throw newNoSessionCreationException();
}
}
} else {
if (this.session == null) {
boolean existing = getSubject().getSession(false) != null;
Session shiroSession = getSubject().getSession(create);
if (shiroSession != null) {
this.session = new ShiroHttpSession(shiroSession, this, this.servletContext);
if (!existing) {
setAttribute(REFERENCED_SESSION_IS_NEW, Boolean.TRUE);
}
}
}
httpSession = this.session;
}
return httpSession;
}
如果this.isHttpSessions()返回true,則返回父類HttpServletRequestWrapper的
也就是servelet規範的session,否則返回ShiroHttpSession物件.
那麼this.isHttpSessions()是什麼呢?
這裡的this.isHttpSessions()取決於this.getSecurityManager().isHttpSessionMode()的值.
我們專案裡SecurityManager設定為DefaultWebSecurityManager.其中isHttpSessionMode()方法為:
我們這個專案使用的sessionManager是DefaultWebSessionManager,DefaultWebSessionManager實現了sessionManager 介面. 但是DefaultWebSessionManager中該方法返回的是false.
public boolean isServletContainerSessions() {
return false;
}
所以最終session得到的是ShiroHttpSession.
如果不想讓Session轉換為ShiroHttpSession,我們可以自己實現sessionManager
<!-- sessionManager -->
<!-- <bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager"> -->
<bean id="sessionManager" class="com.unis.module.auth.shiro.MyWebSessionManager">
<property name="globalSessionTimeout" value="7200000" />
<property name="sessionDAO" ref="redisSessionDAO" />
<property name="sessionValidationScheduler" ref="sessionValidationScheduler"/>
<property name="sessionValidationSchedulerEnabled" value="true"/>
<property name="sessionIdCookie" ref="wapsession"/>
</bean>
public class MyWebSessionManager extends DefaultWebSessionManager{
@Override
public boolean isServletContainerSessions() {
return true;
}
}