1. 程式人生 > >Shiro源代碼分析之兩種Session的方式

Shiro源代碼分析之兩種Session的方式

amp msg cto 開源 request cannot pad turn ssa

1Shiro默認的Session處理方式

 <!-- 定義 Shiro 主要業務對象 -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <!-- <property name="sessionManager" ref="sessionManager" /> -->
        <property name="realm" ref="systemAuthorizingRealm" />
<property name="cacheManager" ref="shiroCacheManager" /> </bean>

這裏從DefaultWebSecurityManager這裏看起。這個代碼是定義的Shiro安全管理對象,看以下的構造方法(代碼 1-1

(代碼 1-1

public DefaultWebSecurityManager() {
        super();
        ((DefaultSubjectDAO) this.subjectDAO).setSessionStorageEvaluator(new DefaultWebSessionStorageEvaluator());
this.sessionMode = HTTP_SESSION_MODE; setSubjectFactory(new DefaultWebSubjectFactory()); setRememberMeManager(new CookieRememberMeManager()); setSessionManager(new ServletContainerSessionManager()); }



從 構造方法裏面能夠看出,這裏面有 publicvoid setRememberMeManager(RememberMeManager rememberMeManager)

publicvoid setSessionManager(SessionManager sessionManager)兩個方法。這兩個各自是對ShiroremembereMe功能和Session功能的管理。當中在構造方法裏面能夠看到,事實上一開是就設置了SessionManager。就是默認的:ServletContainerSessionManager().接下來看看默認的ServletContainerSessionManager()是怎麽玩轉Session.看下圖。這個圖顯示了這個類的這些個方法

技術分享

這個類通過getSession(SessionKey)獲得Sesison,以下看看這種方法幹了些什麽(代碼1-2

(代碼1-2

publicSession getSession(SessionKey key) throws SessionException {

        if (!WebUtils.isHttp(key)) { //推斷是不是httpkey,否則拋異常
            String msg = "SessionKey must be an HTTP compatible implementation.";
            throw new IllegalArgumentException(msg);
        }
        HttpServletRequest request = WebUtils.getHttpRequest(key); //通過工具類獲得HttpServletRequest 這類是javax.servlet.http.HttpServletRequest;
        Session session = null;
        HttpSession httpSession = request.getSession(false);//先從request裏獲得本來存在的
        if (httpSession != null) {
            session = createSession(httpSession, request.getRemoteHost());//假設不為空,就創建一個封裝了的,為空就無論它
        }
        return session;
    }
    

能夠看看凝視,凝視上都寫好了。這裏的意思是。首先推斷封裝好的Key是不是Httpkey。假設是就繼續,不是就拋異常.key這個是帶了ServletRequestServletResponseWebSessinKey,詳細怎麽new出來的,看DefaultWebSecurityManager的這方法代碼就知道了(代碼1-3。這裏裏,將RequestResponse還有sessionId傳遞進去

(代碼1-3

@Override

    protected SessionKey getSessionKey(SubjectContext context) {
        if (WebUtils.isWeb(context)) {
            Serializable sessionId = context.getSessionId();
            ServletRequest request = WebUtils.getRequest(context);
            ServletResponse response = WebUtils.getResponse(context);
            return new WebSessionKey(sessionId, request, response);
        } else {
            return super.getSessionKey(context);
        }
    }



 由於繼承了 org.apache.shiro.session.mgt.DefaultSessionKey ,這個類是實現了SessionKey這個接口。

看看createSession這種方法,這種方法就是將傳入的HttpSessionhost傳進去,封裝成ShiroHttpServletSession (代碼 1-4

(代碼1-4

protectedSession createSession(HttpSession httpSession, String host) {

        return new HttpServletSession(httpSession, host);
    }



 

關於默認的Session管理器,最後還看一下HttpServletSession這個類。就看一下的幾個方法。還有些方法是沒有放出來的,能夠明顯的看出。Shiro使用了一個叫Session的接口。但這個接口是和HttpSession的接口一模一樣。就是通過HttpSession這個接口獲得Session的功能(代碼1-5

(代碼1-5

public class HttpServletSession implements Session {
    private HttpSession httpSession = null;
    public HttpServletSession(HttpSession httpSession, String host) {
        if (httpSession == null) {
            String msg = "HttpSession constructor argument cannot be null.";
            throw new IllegalArgumentException(msg);
        }
        if (httpSession instanceof ShiroHttpSession) {
            String msg = "HttpSession constructor argument cannot be an instance of ShiroHttpSession.  This " +
                    "is enforced to prevent circular dependencies and infinite loops.";
            throw new IllegalArgumentException(msg);
        }
        this.httpSession = httpSession;
        if (StringUtils.hasText(host)) {
            setHost(host);
        }
    }
……………………
……………………
………………
    public Object getAttribute(Object key) throws InvalidSessionException {
        try {
            return httpSession.getAttribute(assertString(key));
        } catch (Exception e) {
            throw new InvalidSessionException(e);
        }
    }
    public void setAttribute(Object key, Object value) throws InvalidSessionException {
        try {
            httpSession.setAttribute(assertString(key), value);
        } catch (Exception e) {
            throw new InvalidSessionException(e);
        }
    }
 ………………
 ………………
}



默認的模式就描寫敘述到這裏



2、接下來說另外一種方式。就是廢棄掉tomcat自己的Session,使用企業級Session方案。這樣的方案能夠和容器無關。但在我們項目沒有使用,由於用了開源連接池druid貌似logout的時候有點不正確。

    <bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
        <property name="globalSessionTimeout" value="3600000" />
        <property name="sessionDAO" ref="sessionDAO" />
    </bean>
    <bean id="sessionDAO" class="org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO">
        <property name="activeSessionsCacheName" value="shiro-activeSessionCache" />
        <property name="cacheManager" ref="shiroCacheManager" />
    </bean> 
 
    <!-- 用戶授權信息Cache, 採用EhCache -->
    <bean id="shiroCacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
        <property name="cacheManager" ref="cacheManager"/>
    </bean>

必須向Spring註冊EnterpriseCacheSessionDAO然後將cacheManager註入進去

這樣的配置的創建入口在SecurityUtils.getSubject().getSession();這裏看以下代碼(代碼2-1

(代碼2-1

publicSession getSession(boolean create) {

            if (log.isTraceEnabled()) {
                log.trace("attempting to get session; create = " + create +
                        "; session is null = " + (this.session == null) +
                        "; session has id = " + (this.session != null && session.getId() != null));
            }
            if (this.session == null && create) {//session為空,而且能創建
         ……
         ……省略
         ……
                SessionContext sessionContext = createSessionContext();
                Session session = this.securityManager.start(sessionContext);//在這裏創建Session
                this.session = decorate(session);//包裝Session,他自己建的自己也去包裝一下
            }



      

調用DefaultSecurityManager的父類SessionsSecurityManagerSessionstart(SessionContext context)。以下是這種方法的代碼(代碼2-2

(代碼2-2

public Session start(SessionContext context) throws AuthorizationException {
        return this.sessionManager.start(context);
    }



然後調用sessionManagerstart方法來創建Session。創建Session的入口。就在這裏。看以下代碼分析(代碼2-3

(代碼2-3

publicSession start(SessionContext context) {

        Session session = createSession(context);//創建Session
        applyGlobalSessionTimeout(session);
        onStart(session, context);
        notifyStart(session);
        return createExposedSession(session, context);
    }



創建Session這裏是調用?DefaultSessionManager的父類的createSession,事實上父類也沒有真正來創建Session

這裏用到了模板方法。父類裏面的doCreateSession是抽象方法,最後真正創建子類的還是交給子類去實現(代碼2-4

(代碼2-4

protectedSession createSession(SessionContext context) throwsAuthorizationException {

        enableSessionValidationIfNecessary();
        return doCreateSession(context);
    }
    protected abstract Session doCreateSession(SessionContext initData) throws AuthorizationException;



?其它的也沒多少可分析的。這裏再看一下manager裏面的sessionFacotry工廠的createSession方法(代碼2-5

(代碼2-5

publicSession createSession(SessionContext initData) {

        if (initData != null) {
            String host = initData.getHost();
            if (host != null) {
                return new SimpleSession(host);
            }
        }
        return new SimpleSession();
    }
 

這裏的SimpleSession是實現了Session接口的。詳細能夠看看相關的類繼承圖

另外Session是怎麽緩存進入Cache的呢?在以下的調用以下代碼創建Session的過程中,以下方法會調用,而緩存就在create(s)這裏面(代碼2-6

(代碼2-6

protected Session doCreateSession(SessionContext context) {
        Session s = newSessionInstance(context);
        if (log.isTraceEnabled()) {
            log.trace("Creating session for host {}", s.getHost());
        }
        create(s);
        return s;
    }
  

經過一些步驟之後在CachingSessionDao裏被緩存,以下是代碼。

能夠看以下的凝視(代碼2-7

(代碼2-7

protectedvoid cache(Session session, Serializable sessionId) {

        if (session == null || sessionId == null) {
            return;
        }
        Cache<Serializable, Session> cache = getActiveSessionsCacheLazy();//獲取緩存
        if (cache == null) {
            return;
        }
        cache(session, sessionId, cache);//有緩存就存起來
    }



  

以上是Session的創建過程,獲取Session就簡單說吧。有些過程自己發現更有趣。這裏會調用DefaultWebSessionManager的父類的getAttribute這種方法(代碼2-8

(代碼2-8

publicObject getAttribute(SessionKey sessionKey, Object attributeKey)throws InvalidSessionException {

        return lookupRequiredSession(sessionKey).getAttribute(attributeKey);
    }



?最後會調用CachingSessionDao的這個publicSession readSession(Serializable sessionId) throwsUnknownSessionException 在這裏就會從緩存裏讀取Session(代碼2-9

(代碼2-9

 public Session readSession(Serializable sessionId) throws UnknownSessionException {
        Session s = getCachedSession(sessionId);
        if (s == null) {
            s = super.readSession(sessionId);
        }
        return s;
    }
   

?這是getCachedSession(sessionId)的代碼。看了代碼想必非常easy理解了吧(代碼2-10

    protected Session getCachedSession(Serializable sessionId) {
        Session cached = null;
        if (sessionId != null) {
            Cache<Serializable, Session> cache = getActiveSessionsCacheLazy();
            if (cache != null) {
                cached = getCachedSession(sessionId, cache);
            }
        }
        return cached;
    }



?獲得了Session,要獲得裏面的值和對象就非常easy了

有問題歡迎提出來。由於是先寫在編輯器上的。然後在復制到word上。所以代碼是一致的黑色,希望可以講究著看,寫個原創文章不easy,眼睛都看腫了。所以轉載的時候能帶上作者,謝謝

作者:肖華

blog.csdn.net/xh199110 飛丶天

Shiro源代碼分析之兩種Session的方式