shiro(6)- SessionMananger(操作session)

shiro安全控制目錄

Shiro提供了完整的企業(yè)級會話管理功能,不依賴底層容器(如web容器的tomcat)机断,不管是JavaSE還是JavaEE環(huán)境都可以使用钳榨,提供了會話管理,會話監(jiān)聽州藕,會話存儲/持久化,容器無關的集群酝陈,失效/過期支持床玻。對Web的透明支持,SSO單點登錄的支持等特性沉帮。即使用Shiro的會話管理可以直接替換如Web容器的會話管理锈死。

序 什么叫做會話?

會話是用戶訪問應用時保持的連接關系穆壕。

因為http協(xié)議是無狀態(tài)的協(xié)議馅精,所以,需要借助會話(session)來使得應用在多次交互中能夠識別出當前訪問的用戶是誰粱檀。并且可以在多次會話中保存一些數據洲敢。

如訪問一些網站時登錄,網站可以記住用戶茄蚯,且在退出之前都可以識別當前用戶是誰压彭。

1. shiro Session簡單的API

Shiro Session和HttpSession使用方式很像。當然它們最大的區(qū)別在于你可以在任何應用中使用Shiro Session渗常,而不僅僅局限于Web應用壮不。

1. 獲取session對象
Shiro的會話支持不僅可以在普通JavaEE應用中使用,也可以在web應用中使用皱碘,且獲取方式是一致的询一。

Subject subject = SecurityUtils.getSubject();
Session session = subject.getSession();
//這個參數用于判定會話不存在時是否創(chuàng)建新會話。
Session session = subject.getSession(boolean create);

可以使用subject.getSession()獲取會話癌椿,其等價于subject.getSession(true)健蕊,即如果當前沒有創(chuàng)建Session對象,會創(chuàng)建一個踢俄。

2. 獲取會話的唯一標識

session.getId();

3. 獲取主機地址

session.getHost();

獲取當前subject的主機地址缩功,該地址是通過HostAuthenticationToken.getHost()提供的遥诉。

4. 設置會話超時時間

//獲取超時時間
session.getTimeout();
//設置超時時間
session.setTimeout(毫秒);

獲取/設置當前Session的過期時間嗤堰;如果不設置是默認的會話管理器的全局過期時間。

5. 獲取啟動/訪問時間

//獲取會話的啟動時間
session.getStartTimestamp();
//獲取會話的最后訪問時間
session.getLastAccessTime();

獲取會話的啟動時間和最后訪問時間檬寂;如果是JavaSE應用需要自己定期調用session.touch()去更新最后訪問時間琳钉;如果是web應用势木,每次進入ShiroFilter都會自動調用session.touch()來更新最后訪問時間。

6. 更新/刪除會話

//更新會話最后訪問時間
session.touch();
//銷毀session會話
session.stop();

更新會話最后訪問時間及銷毀會話歌懒;當Subject.logout()時會自動調用stop方法來銷毀會話的啦桌。如果在web中,調用javax.servlet.http.HttpSession. invalidate()也會自動調用Shiro Session.stop方法進行銷毀Shiro的會話歼培。

7. 操作會話

session.setAttribute("key", "123");  
Assert.assertEquals("123", session.getAttribute("key"));  
session.removeAttribute("key"); 

設置/獲取/刪除會話屬性震蒋;在整個會話范圍內都可以對這些屬性進行操作。


SessionManager負責創(chuàng)建和管理用戶Session生命周期躲庄,在任何環(huán)境下都可以提供用戶健壯的session體驗查剖。默認情況下,Shiro會使用容器自帶的session機制噪窘,但若是容器不存在session笋庄,那么Shiro會提供內置的企業(yè)級session來管理。當然在開發(fā)中倔监,也可以使用SessionDAO允許數據源持久化Session直砂。

2. 會話管理器

在安全框架領域,Apache Shiro提供了一些獨特的東西浩习,可以在任何應用和架構層一致的使用Session API静暂,即Session不再依賴于Servlet或EJB容器。

Shiro會話最重要的一個好處便是它們獨立于容器谱秽。通過Shiro會話洽蛀,可以獲取一個容器無關的集群解決方案。Shiro架構允許可插拔的會話數據存儲疟赊,如企業(yè)緩存郊供,關系型數據庫,noSQL系統(tǒng)等近哟。并且Shiro會話可以跨客戶端技術進行共享驮审。

值得一提的是Shiro在Web環(huán)境中對會話的支持。

缺省 Http 會話

對于Web應用吉执,Shiro缺省將我們習以為常的Servlet容器會話作為其會話的基礎設施疯淫。即調用subject.getSession()subject.getSession(boolean)方法時,Shiro會返回Servlet容器的HttpSession實例支持的Session實例戳玫。這種方式巧妙之處在于調用subject.getSession()的業(yè)務層代碼會跟一個Shiro Session實例交互峡竣,并且實際上它也會跟基于Web的HttpSession打交道×烤牛可以維護架構層之間的清晰隔離适掰。

Web 層中 Shiro 的原生會話

如果需要Shiro的企業(yè)級會話特性(如與容器無關的集群)而打開了Shiro的原生會話管理。而實際上我們也希望HttpServletRequest.getSession()HttpSession API能和Shiro原生的會話協(xié)作荠列。而Shiro完整實現了Servlet規(guī)范中Session部分以及在Web應用中支持原生會話类浪。這意味著,不管何時你使用相應的HttpServletRequest或HttpSession調用肌似,Shiro都會將這些調用委托給內部的原生會話API费就。即無需修改Web代碼,即使正在使用Shiro內置的Session機制川队,獲取到的Servlet Session和Shiro Session依舊保持一致力细。

詳見——讓 Apache Shiro 保護你的應用

會話管理器

虛線:實現的接口睬澡;
實線:繼承的父類;

Shiro提供了三個默認實現:

  • DefaultSessionManager:用于JavaSE環(huán)境眠蚂;
  • ServletContainerSessionManager:用于Web環(huán)境煞聪,直接使用Servlet容器會話;
  • DefaultWebSessionManager:用于Web環(huán)境逝慧,自己維護會話昔脯,不會使用Servlet容器的會話管理。

3. subject和request獲取Session的區(qū)別

3.1. 兩者方式獲取的session是否相同

1. 在Spring mvc中獲取session有兩種方法:

  1. 使用request對象獲取session
Session session = request.getSession();
  1. 通過shiro獲取session
 Subject currentUser = SecurityUtils.getSubject();
 Session session = currentUser.getSession();

一般在web中笛臣,有兩種會話管理器

  • DefaultWebSessionManager (自己維護會話)
  • ServletContainerSessionManager(默認云稚,直接使用servlet的會話)

而在項目中需要配置shiro的securityManager,因為配置影響了shiro session的來源沈堡。

<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
      <property name="realm" ref="shiroRealm"/>
</bean>

2. 兩種會話操縱的session是否相同静陈?(注:以Servlet會話進行分析)

在controller中打印session,發(fā)現request獲取的會話類型是:org.apache.catalina.session.StandardSessionFacade诞丽,而subject的session類型是org.apache.shiro.subject.support.DelegatingSubject$StoppingAwareProxiedSession

session的類型

在上圖中窿给,我們可以知道request獲取的session明顯是httpSession,而subject獲取的session類型率拒,本質上也是httpSession崩泡。即兩者在操作session時,都是操作的同一類型的session對象猬膨。

3.2 request對象中session的來源

  1. 如何獲取過濾器filter

SpringMVC整合shiro角撞,需要在web.xml中配置filter

 <filter>
    <filter-name>shiroFilter</filter-name>
    <filter-class>
            org.springframework.web.filter.DelegatingFilterProxy
        </filter-class>
    <init-param>
      <param-name>targetFilterLifecycle</param-name>
      <param-value>true</param-value>
    </init-param>
  </filter>
  <filter-mapping>
    <filter-name>shiroFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

DelegateFilterProxy是一個過濾器,準確來說是目的過濾器的代理勃痴,由它在doFilter方法中谒所,獲取spring容器中的過濾器,并調用目標過濾器的doFilter方法沛申。這樣做的好處是:原來的過濾器配置放在web.xml中劣领,現在可以把filter的配置放在spring中,并由spring管理它的生命周期铁材。

DelegatingFilterProxy——將Filter交由Spring管理

我們可以知道尖淘,使用DelegatingFilterProxy那么過濾器的生命周期由Spring來管理。若是沒有指定targetBeanName著觉,那么使用<filter-name>

  • spring.xml配置
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> 
  <!-- Shiro的核心安全接口,這個屬性是必須的 -->  
  <property name="securityManager" ref="securityManager"/>  
  <!-- 要求登錄時的鏈接,非必須的屬性,默認會自動尋找Web工程根目錄下的"/login.jsp"頁面 -->  
  <property name="loginUrl" value="/login/init"/>  
  <!-- 用戶訪問未對其授權的資源時,所顯示的連接 -->  
  <property name="unauthorizedUrl" value="/pages/error/403.jsp"/>  
  <property name="filterChainDefinitions"> 
    <value> 
      <!-- Shiro 過濾鏈的定義 --> 
      /login/init/** = anon 
      <!-- 對于登錄相關不進行鑒權 --> 
      /login/getVerifyCode/** = anon
      <!-- 對于注冊相關不進行鑒權 -->
      /register/** = anon
      <!-- 靜態(tài)資源不進行鑒權 -->
      /static/** = anon
    </value> 
  </property>  
  <property name="filters"> 
    <map> 
      <entry key="user" value-ref="userFilter"/> 
    </map> 
  </property> 
</bean>

熟悉spring的應該知道村生,bean的工廠是用來生產相關的bean,并將bean注冊到spring容器中饼丘。通過查看工廠bean的getObject方法趁桃,可見,委托類調用的filter類型是SpringShiroFilter。

SpringShiroFilter類圖

既然SpringShiroFilter屬于過濾器卫病,那么肯定有一個doFilter方法油啤,doFilter由它的父類OncePerRequestFilter實現。

OncePerRequestFilter在doFilter方法中蟀苛,判斷是否在request中有"already filtered"這個屬性設置為true益咬,如果有,則交給下一個過濾器屹逛,如果沒有,就執(zhí)行doFilterInternal()抽象方法汛骂。

doFilterInternal由AbstractShiroFilter類實現罕模,即SpringShiroFilter的直屬父類實現。

protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain)
            throws ServletException, IOException {

            //包裝request/response
            final ServletRequest request = prepareServletRequest(servletRequest, servletResponse, chain);
            final ServletResponse response = prepareServletResponse(request, servletResponse, chain);

            //創(chuàng)建subject帘瞭,其實創(chuàng)建的是Subject的代理類DelegatingSubject
            final Subject subject = createSubject(request, response);
            
             // 繼續(xù)執(zhí)行過濾器鏈淑掌,此時的request/response是前面包裝好的request/response
            subject.execute(new Callable() {
                public Object call() throws Exception {
                    updateSessionLastAccessTime(request, response);
                    executeChain(request, response, chain);
                    return null;
                }
            });
    }

在doFilterInternal中,可以看到對ServletRequest和ServletResponse進行了包裝蝶念,除此之外抛腕,還把包裝后的request/response作為參數,創(chuàng)建了Subject媒殉,這個subject其實就是代理類DelegatingSubject担敌。

那么這個包裝后的request是什么呢?

我們繼續(xù)解析prepareServletRequest廷蓉。

protected ServletRequest prepareServletRequest(ServletRequest request, ServletResponse response, FilterChain chain) {
        ServletRequest toUse = request;
        if (request instanceof HttpServletRequest) {
            HttpServletRequest http = (HttpServletRequest) request;
            toUse = wrapServletRequest(http);  //真正去包裝request的方法
        }
        return toUse;
    }
 protected ServletRequest wrapServletRequest(HttpServletRequest orig) {
        //看看看全封,ShiroHttpServletRequest
        return new ShiroHttpServletRequest(orig, getServletContext(), isHttpSessions());  
    }

由此我們可以看到controller獲取到的ShiroHttpServletRequest對象。

ShiroHttpServletRequest構造方法的第三個參數是關鍵參數桃犬。進入ShiroHttpServletRequest里面看看它有什么用刹悴?

  • 在getRequestedSessionId()方法用到,獲取sessionId攒暇。
  • 在getSession()用到土匀,獲取session會話對象。

(1)先看下getRequestedSessionId()形用。isHttpSessions決定sessionid是否來自servlet就轧。

public String getRequestedSessionId() {
        String requestedSessionId = null;
        if (isHttpSessions()) {
            requestedSessionId = super.getRequestedSessionId();   //從servlet中獲取sessionid
        } else {
            Object sessionId = getAttribute(REFERENCED_SESSION_ID);   //從request中獲取REFERENCED_SESSION_ID這個屬性
            if (sessionId != null) {
                requestedSessionId = sessionId.toString();
            }
        }

        return requestedSessionId;
    }

(2)再看下getSession。isHttpSession決定了session是否來自servlet田度。

public HttpSession getSession(boolean create) {

        HttpSession httpSession;

        if (isHttpSessions()) {
            httpSession = super.getSession(false);  //從servletRequest獲取session
            if (httpSession == null && create) {
                if (WebUtils._isSessionCreationEnabled(this)) {
                    httpSession = super.getSession(create);  //從servletRequest獲取session
                } else {
                    throw newNoSessionCreationException();
                }
            }
        } else {
            if (this.session == null) {

                boolean existing = getSubject().getSession(false) != null; //從subject中獲取session

                Session shiroSession = getSubject().getSession(create); //從subject中獲取session
                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;
    }

既然isHttpSessions()如此重要钓丰,那么我們要看下在什么情況下,他返回true每币。

   protected boolean isHttpSessions() {
        return getSecurityManager().isHttpSessionMode();
    }

isHttpSessions是否返回true是由shiro安全管理器isHttpSessionMode()決定的携丁。我們使用的安全管理器是DefaultWebSecurityManager,我們在DefaultWebSecurityManager的源碼找到isHttpSessionMode()方法。

public boolean isHttpSessionMode() {
        SessionManager sessionManager = getSessionManager();
        return sessionManager instanceof WebSessionManager && ((WebSessionManager)sessionManager).isServletContainerSessions();
    }

需要注意的是:在配置文件中梦鉴,我們并沒有配置SessionManager李茫,安全管理器會使用會話管理器ServletContainerSessionManager,在ServletContainerSessionManager中肥橙,isServletContainerSessions返回true魄宏。

因此,在前面的配置中存筏,request中獲取的session將是servlet context下的session宠互。

3.3. subject的session來源

前面的doFilterInternal的分析中,還提到了subject創(chuàng)建的過程椭坚。接著我們繼續(xù)分析該過程予跌,判斷subject中的session的來源。

在controller中subject獲取session

 Subject currentUser = SecurityUtils.getSubject();
 Session session = currentUser.getSession();

我們看下shiro定義的session類圖善茎,具有一些與HttpSession相同的方法券册,例如setAttribute和getAttribute。

session類圖

在doFilterInternal中垂涯,shiro把包裝后的request/response作為參數烁焙,創(chuàng)建subject

final Subject subject = createSubject(request, response);

最終,由DefaultWebSubjectFactory創(chuàng)建subject耕赘,并把principals [資本 普瑞色跑死], session, request, response, securityManager參數封裝到subject骄蝇。由于第一次創(chuàng)建session,此時session沒有實例操骡。

那么乞榨,當我們第一次調用subject.getSession()嘗試獲取session時,發(fā)生了什么当娱?從前面的代碼我們知道吃既,我們獲取到的subject是WebDelegatingSubject類型,它的父類DelegatingSubject實現了getSession方法跨细。

public Session getSession(boolean create) {
        if (this.session == null && create) {
            // 創(chuàng)建session上下文鹦倚,上下文里面封裝有request/response/host
            SessionContext sessionContext = createSessionContext();
            // 根據上下文,由securityManager創(chuàng)建session
            Session session = this.securityManager.start(sessionContext);
            // 包裝session
            this.session = decorate(session);
        }
        return this.session;
    }

接下來解析一下冀惭,securityManager根據sessionContext 創(chuàng)建session這個流程震叙。它是交由sessionManager會話管理器進行會話創(chuàng)建。這里的sessionManager其實就是ServletContainerSessionManager類散休,找到它的createSession方法媒楼。

protected Session createSession(SessionContext sessionContext) throws AuthorizationException {
        HttpServletRequest request = WebUtils.getHttpRequest(sessionContext);

        // 從request中獲取HttpSession
        HttpSession httpSession = request.getSession();

        String host = getHost(sessionContext);

        // 包裝成 HttpServletSession 
        return createSession(httpSession, host);
    }

這里就可以知道,其實Session是來源于request的HttpSession戚丸,也就是說划址,來源上一個過濾器中的request的HttpSession扔嵌。HttpSession以成員變量的形式存在HttpServletSession中。并且從安全管理器獲取HttpServletSession后夺颤,還調用decorate()裝飾session痢缎,裝飾后的session類型就是StoppingAwareProxiedSession,HttpServletSession就是它的成員世澜。

session的getAttribute和addAttribute方法独旷,StoppingAwareProxiedSession做了些什么?

它是由父類ProxiedSession實現session.getAttribute和session.addAttribute方法寥裂。

 public Object getAttribute(Object key) throws InvalidSessionException {
        return delegate.getAttribute(key);
    }
    public void setAttribute(Object key, Object value) throws InvalidSessionException {
        delegate.setAttribute(key, value);
    }

可見嵌洼,getAttribute和addAttribute由委托類delegate完成,這里的delegate就是HttpServletSession封恰。

 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);
        }
    }

最后總結一下麻养,通過request.getSeesion()與subject.getSeesion()獲取session后,對session的操作是相同的俭驮。而session的來源是servletRequest還是shiro回溺。主要是由安全管理器SecurityManager和SessionManager會話管理器決定春贸。

參考文檔:
springmvc集成shiro后混萝,session、request姓汪還是姓蔣萍恕?

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末逸嘀,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子允粤,更是在濱河造成了極大的恐慌崭倘,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,884評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件类垫,死亡現場離奇詭異司光,居然都是意外死亡,警方通過查閱死者的電腦和手機悉患,發(fā)現死者居然都...
    沈念sama閱讀 90,755評論 3 385
  • 文/潘曉璐 我一進店門残家,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人售躁,你說我怎么就攤上這事坞淮。” “怎么了陪捷?”我有些...
    開封第一講書人閱讀 158,369評論 0 348
  • 文/不壞的土叔 我叫張陵回窘,是天一觀的道長。 經常有香客問我市袖,道長啡直,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,799評論 1 285
  • 正文 為了忘掉前任,我火速辦了婚禮付枫,結果婚禮上烹玉,老公的妹妹穿的比我還像新娘。我一直安慰自己阐滩,他們只是感情好二打,可當我...
    茶點故事閱讀 65,910評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著掂榔,像睡著了一般继效。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上装获,一...
    開封第一講書人閱讀 50,096評論 1 291
  • 那天瑞信,我揣著相機與錄音,去河邊找鬼穴豫。 笑死凡简,一個胖子當著我的面吹牛,可吹牛的內容都是我干的精肃。 我是一名探鬼主播秤涩,決...
    沈念sama閱讀 39,159評論 3 411
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼司抱!你這毒婦竟也來了筐眷?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,917評論 0 268
  • 序言:老撾萬榮一對情侶失蹤习柠,失蹤者是張志新(化名)和其女友劉穎匀谣,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體资溃,經...
    沈念sama閱讀 44,360評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡武翎,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,673評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了溶锭。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片宝恶。...
    茶點故事閱讀 38,814評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖暖途,靈堂內的尸體忽然破棺而出卑惜,到底是詐尸還是另有隱情,我是刑警寧澤驻售,帶...
    沈念sama閱讀 34,509評論 4 334
  • 正文 年R本政府宣布露久,位于F島的核電站,受9級特大地震影響欺栗,放射性物質發(fā)生泄漏毫痕。R本人自食惡果不足惜征峦,卻給世界環(huán)境...
    茶點故事閱讀 40,156評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望消请。 院中可真熱鬧栏笆,春花似錦、人聲如沸臊泰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽缸逃。三九已至针饥,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間需频,已是汗流浹背丁眼。 一陣腳步聲響...
    開封第一講書人閱讀 32,123評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留昭殉,地道東北人苞七。 一個月前我還...
    沈念sama閱讀 46,641評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像挪丢,于是被迫代替她去往敵國和親蹂风。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,728評論 2 351

推薦閱讀更多精彩內容

  • 這部分主要是與Java Web和Web Service相關的面試題吃靠。 96硫眨、闡述Servlet和CGI的區(qū)別? 答...
    雜貨鋪老板閱讀 1,401評論 0 10
  • 問題描述 之前在公司搭項目平臺的時候權限框架采用的是shiro,由于系統(tǒng)主要面向的是APP端的用戶,PC端僅僅是公...
    Briseis閱讀 34,002評論 4 28
  • 1課程回顧 Servlet編程 1)Servlet生命周期(重點) 構造方法:創(chuàng)建servlet對象足淆。默認情況下巢块,...
    守亭翁閱讀 213評論 0 0
  • 一. Java基礎部分.................................................
    wy_sure閱讀 3,807評論 0 11
  • 我大學 學的「計算機科學與技術專業(yè)」,是在大一的時候巧号,想聽聽其他老師上的專業(yè)課(C language)族奢,然后就去旁...
    ColdRomantic閱讀 364評論 0 0