第10章 會話管理

      Shiro提供了完整的企業級會話管理功能,不依賴於底層容器(如web容器tomcat),無論JavaSE仍是JavaEE環境均可以使用,提供了會話管理、會話事件監聽、會話存儲/持久化、容器無關的集羣、失效/過時支持、對Web的透明支持、SSO單點登陸的支持等特性,即直接使用Shiro的會話管理能夠直接替換如Web容器的會話管理。java

會話git

      所謂會話,即用戶訪問應用時保持的鏈接關係,在屢次交互中應用可以識別出當前訪問的用戶是誰,且能夠在屢次交互中保存一些數據,如訪問一些網站時登陸成功後,網站能夠記住用戶,且在退出以前均可以識別當前用戶是誰,Shiro的會話不只能夠在普通的JavaSE應用中使用,也能夠在JavaEE應用中使用,如web應用,且使用方式是一致的。github

      1. login("classpath:shiro.ini", "zhang", "123"); web

      2. Subject subject = SecurityUtils.getSubject(); sql

      3. Session session = subject.getSession();  數據庫

     登陸成功後使用Subject.getSession()便可獲取會話;apache

    其等價於Subject.getSession(true),即若是當前沒有建立Session對象會建立一個;瀏覽器

    另外Subject.getSession(false),若是當前沒有建立Session則返回null(不過默認狀況下若是啓用會話存儲功能的話在建立Subject時會主動建立一個Session)。緩存

 

1) session.getId();   獲取當前會話的惟一標識。tomcat

2)session.getHost(); 獲取當前Subject的主機地址,該地址是經過HostAuthenticationToken.getHost()提供的。

3) session.getTimeout();   session.setTimeout(毫秒);   獲取/設置當前Session的過時時間;若是不設置默認是會話管理器的全局過時時間。

4)session.getStartTimestamp();   session.getLastAccessTime();  獲取會話的啓動時間及最後訪問時間;若是是JavaSE應用須要本身按期調用session.touch()去更新最後訪問時間;若是是Web應用,每次進入ShiroFilter都會自動調用session.touch()來更新最後訪問時間。

5)session.touch();   session.stop(); 更新會話最後訪問時間及銷燬會話;當Subject.logout()時會自動調用stop方法來銷燬會話。若是在web中,調用javax.servlet.http.HttpSession. invalidate()也會自動調用Shiro Session.stop方法進行銷燬Shiro的會話。

6)session.setAttribute("key", "123"); 

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

   session.removeAttribute("key"); 

   設置/獲取/刪除會話屬性;在整個會話範圍內均可以對這些屬性進行操做。

Shiro提供的會話能夠用於JavaSE/JavaEE環境,不依賴於任何底層容器,能夠獨立使用,是完整的會話模塊。

會話管理器

    會話管理器管理着應用中全部Subject的會話的建立、維護、刪除、失效、驗證等工做。是Shiro的核心組件,頂層組件SecurityManager直接繼承了SessionManager,且提供了SessionsSecurityManager實現直接把會話管理委託給相應的SessionManager,DefaultSecurityManager及DefaultWebSecurityManager默認SecurityManager都繼承了SessionsSecurityManager。

SecurityManager提供了以下接口:

1. Session start(SessionContext context); //啓動會話

2. Session getSession(SessionKey key) throws SessionException; //根據會話Key獲取會話

    另外用於Web環境的WebSessionManager又提供了以下接口:

1. boolean isServletContainerSessions();//是否使用Servlet容器的會話

    Shiro還提供了ValidatingSessionManager用於驗資並過時會話:

2. void validateSessions();//驗證全部會話是否過時

 

Shiro提供了三個默認實現:

DefaultSessionManager:DefaultSecurityManager使用的默認實現,用於JavaSE環境;

ServletContainerSessionManager:DefaultWebSecurityManager使用的默認實現,用於Web環境,其直接使用Servlet容器的會話;

DefaultWebSessionManager:用於Web環境的實現,能夠替代ServletContainerSessionManager,本身維護着會話,直接廢棄了Servlet容器的會話管理。

替換SecurityManager默認的SessionManager能夠在ini中配置(shiro.ini):

[main] 

1. sessionManager=org.apache.shiro.session.mgt.DefaultSessionManager 

2. securityManager.sessionManager=$sessionManager  

Web環境下的ini配置(shiro-web.ini):

<!--EndFragment-->

1. [main] 

2. sessionManager=org.apache.shiro.web.session.mgt.ServletContainerSessionManager 

3. securityManager.sessionManager=$sessionManager 

另外能夠設置會話的全局過時時間(毫秒爲單位),默認30分鐘:

1. sessionManager. globalSessionTimeout=1800000

默認狀況下globalSessionTimeout將應用給全部Session。能夠單獨設置每一個Session的timeout屬性來爲每一個Session設置其超時時間。

另外若是使用ServletContainerSessionManager進行會話管理,Session的超時依賴於底層Servlet容器的超時時間,能夠在web.xml中配置其會話的超時時間(分鐘爲單位):

1. <session-config> 

2.   <session-timeout>30</session-timeout> 

3. </session-config> 

在Servlet容器中,默認使用JSESSIONID Cookie維護會話,且會話默認是跟容器綁定的;在某些狀況下可能須要使用本身的會話機制,此時咱們可使用DefaultWebSessionManager來維護會話:

 

1. sessionIdCookie=org.apache.shiro.web.servlet.SimpleCookie 

2. sessionManager=org.apache.shiro.web.session.mgt.DefaultWebSessionManager 

3. sessionIdCookie.name=sid 

4. #sessionIdCookie.domain=si shu ok.com[中間沒空格]

5. #sessionIdCookie.path= 

6. sessionIdCookie.maxAge=1800

7. sessionIdCookie.httpOnly=true

8. sessionManager.sessionIdCookie=$sessionIdCookie 

9. sessionManager.sessionIdCookieEnabled=true

10. securityManager.sessionManager=$sessionManager 

sessionIdCookie是sessionManager建立會話Cookie的模板:

sessionIdCookie.name:設置Cookie名字,默認爲JSESSIONID;

sessionIdCookie.domain:設置Cookie的域名,默認空,即當前訪問的域名;

sessionIdCookie.path:設置Cookie的路徑,默認空,即存儲在域名根下;

sessionIdCookie.maxAge:設置Cookie的過時時間,秒爲單位,默認-1表示關閉瀏覽器時過時Cookie;

sessionIdCookie.httpOnly:若是設置爲true,則客戶端不會暴露給客戶端腳本代碼,使用HttpOnly cookie有助於減小某些類型的跨站點腳本攻擊;此特性須要實現了Servlet 2.5 MR6及以上版本的規範的Servlet容器支持;

sessionManager.sessionIdCookieEnabled:是否啓用/禁用Session Id Cookie,默認是啓用的;若是禁用後將不會設置Session Id Cookie,即默認使用了Servlet容器的JSESSIONID,且經過URL重寫(URL中的「;JSESSIONID=id」部分)保存Session Id。

另外咱們能夠如「sessionManager. sessionIdCookie.name=sid」這種方式操做Cookie模板。

會話監聽器

會話監聽器用於監聽會話建立、過時及中止事件:

1. public class MySessionListener1 implements SessionListener { 

2. @Override

3. public void onStart(Session session) {//會話建立時觸發

4.         System.out.println("會話建立:" + session.getId()); 

5.     } 

6. @Override

7. public void onExpiration(Session session) {//會話過時時觸發

8.         System.out.println("會話過時:" + session.getId()); 

9.     } 

10. @Override

11. public void onStop(Session session) {//退出/會話過時時觸發

12.         System.out.println("會話中止:" + session.getId()); 

13.     }   

14. } 

若是隻想監聽某一個事件,能夠繼承SessionListenerAdapter實現:

1. public class MySessionListener2 extends SessionListenerAdapter { 

2. @Override

3. public void onStart(Session session) { 

4.         System.out.println("會話建立:" + session.getId()); 

5.     } 

  1. }  public class MySessionListener2 extends SessionListenerAdapter {

在shiro-web.ini配置文件中能夠進行以下配置設置會話監聽器:

1. sessionListener1=com.github.zhangkaitao.shiro.chapter10.web.listener.MySessionListener1 

2. sessionListener2=com.github.zhangkaitao.shiro.chapter10.web.listener.MySessionListener2 

3. sessionManager.sessionListeners=$sessionListener1,$sessionListener2 

會話存儲/持久化

Shiro提供SessionDAO用於會話的CRUD,即DAO(Data Access Object)模式實現:

1. //如DefaultSessionManager在建立完session後會調用該方法;如保存到關係數據庫/文件系統/NoSQL數據庫;便可以實現會話的持久化;返回會話ID;主要此處返回的ID.equals(session.getId());

2. Serializable create(Session session); 

3. //根據會話ID獲取會話

4. Session readSession(Serializable sessionId) throws UnknownSessionException; 

5. //更新會話;如更新會話最後訪問時間/中止會話/設置超時時間/設置移除屬性等會調用

6. void update(Session session) throws UnknownSessionException; 

7. //刪除會話;當會話過時/會話中止(如用戶退出時)會調用

8. void delete(Session session); 

9. //獲取當前全部活躍用戶,若是用戶量多此方法影響性能

10. Collection<Session> getActiveSessions();  

Shiro內嵌了以下SessionDAO實現:

AbstractSessionDAO提供了SessionDAO的基礎實現,如生成會話ID等;CachingSessionDAO提供了對開發者透明的會話緩存的功能,只須要設置相應的CacheManager便可;MemorySessionDAO直接在內存中進行會話維護;而EnterpriseCacheSessionDAO提供了緩存功能的會話維護,默認狀況下使用MapCache實現,內部使用ConcurrentHashMap保存緩存的會話。

能夠經過以下配置設置SessionDAO:

1. sessionDAO=org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO 

2. sessionManager.sessionDAO=$sessionDAO  

Shiro提供了使用Ehcache進行會話存儲,Ehcache能夠配合TerraCotta實現容器無關的分佈式集羣。

首先在pom.xml裏添加以下依賴:

1. <dependency> 

2.     <groupId>org.apache.shiro</groupId> 

3.     <artifactId>shiro-ehcache</artifactId> 

4.     <version>1.2.2</version> 

5. </dependency>  

 

接着配置shiro-web.ini文件:

1. sessionDAO=org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO 

2. sessionDAO. activeSessionsCacheName=shiro-activeSessionCache 

3. sessionManager.sessionDAO=$sessionDAO 

4. cacheManager = org.apache.shiro.cache.ehcache.EhCacheManager 

5. cacheManager.cacheManagerConfigFile=classpath:ehcache.xml 

6. securityManager.cacheManager = $cacheManager  

 

sessionDAO. activeSessionsCacheName:設置Session緩存名字,默認就是shiro-activeSessionCache;

cacheManager:緩存管理器,用於管理緩存的,此處使用Ehcache實現;

cacheManager.cacheManagerConfigFile:設置ehcache緩存的配置文件;

securityManager.cacheManager:設置SecurityManager的cacheManager,會自動設置實現了CacheManagerAware接口的相應對象,如SessionDAO的cacheManager;

而後配置ehcache.xml:

1. <cache name="shiro-activeSessionCache"

2.        maxEntriesLocalHeap="10000"

3.        overflowToDisk="false"

4.        eternal="false"

5.        diskPersistent="false"

6.        timeToLiveSeconds="0"

7.        timeToIdleSeconds="0"

8.        statistics="true"/>  

 

Cache的名字爲shiro-activeSessionCache,即設置的sessionDAO的activeSessionsCacheName屬性值。

另外能夠經過以下ini配置設置會話ID生成器:

1. sessionIdGenerator=org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator 

2. sessionDAO.sessionIdGenerator=$sessionIdGenerator  

 

用於生成會話ID,默認就是JavaUuidSessionIdGenerator,使用java.util.UUID生成。

若是自定義實現SessionDAO,繼承CachingSessionDAO便可:

1. public class MySessionDAO extends CachingSessionDAO { 

2. private JdbcTemplate jdbcTemplate = JdbcTemplateUtils.jdbcTemplate(); 

3. protected Serializable doCreate(Session session) { 

4.         Serializable sessionId = generateSessionId(session); 

5.         assignSessionId(session, sessionId); 

6.         String sql = "insert into sessions(id, session) values(?,?)"; 

7.         jdbcTemplate.update(sql, sessionId, SerializableUtils.serialize(session)); 

8. return session.getId(); 

9.     } 

10. protected void doUpdate(Session session) { 

11. if(session instanceof ValidatingSession && !((ValidatingSession)session).isValid()) { 

12. return; //若是會話過時/中止 不必再更新了

13.     } 

14.         String sql = "update sessions set session=? where id=?"; 

15.         jdbcTemplate.update(sql, SerializableUtils.serialize(session), session.getId()); 

16.     } 

17. protected void doDelete(Session session) { 

18.         String sql = "delete from sessions where id=?"; 

19.         jdbcTemplate.update(sql, session.getId()); 

20.     } 

21. protected Session doReadSession(Serializable sessionId) { 

22.         String sql = "select session from sessions where id=?"; 

23.         List<String> sessionStrList = jdbcTemplate.queryForList(sql, String.class, sessionId); 

24. if(sessionStrList.size() == 0) return null; 

25. return SerializableUtils.deserialize(sessionStrList.get(0)); 

26.     } 

27. }  

doCreate/doUpdate/doDelete/doReadSession分別表明建立/修改/刪除/讀取會話;此處經過把會話序列化後存儲到數據庫實現;接着在shiro-web.ini中配置:

1. sessionDAO=com.github.zhangkaitao.shiro.chapter10.session.dao.MySessionDAO 

其餘設置和以前同樣,由於繼承了CachingSessionDAO;全部在讀取時會先查緩存中是否存在,若是找不到纔到數據庫中查找。

會話驗證

Shiro提供了會話驗證調度器,用於按期的驗證會話是否已過時,若是過時將中止會話;出於性能考慮,通常狀況下都是獲取會話時來驗證會話是否過時並中止會話的;可是如在web環境中,若是用戶不主動退出是不知道會話是否過時的,所以須要按期的檢測會話是否過時,Shiro提供了會話驗證調度器SessionValidationScheduler來作這件事情。

能夠經過以下ini配置開啓會話驗證:

1. sessionValidationScheduler=org.apache.shiro.session.mgt.ExecutorServiceSessionValidationScheduler 

2. sessionValidationScheduler.interval = 3600000

3. sessionValidationScheduler.sessionManager=$sessionManager 

4. sessionManager.globalSessionTimeout=1800000

5. sessionManager.sessionValidationSchedulerEnabled=true

6. sessionManager.sessionValidationScheduler=$sessionValidationScheduler  

sessionValidationScheduler:會話驗證調度器,sessionManager默認就是使用ExecutorServiceSessionValidationScheduler,其使用JDK的ScheduledExecutorService進行按期調度並驗證會話是否過時;

sessionValidationScheduler.interval:設置調度時間間隔,單位毫秒,默認就是1小時;

sessionValidationScheduler.sessionManager:設置會話驗證調度器進行會話驗證時的會話管理器;

sessionManager.globalSessionTimeout:設置全局會話超時時間,默認30分鐘,即若是30分鐘內沒有訪問會話將過時;

sessionManager.sessionValidationSchedulerEnabled:是否開啓會話驗證器,默認是開啓的;

sessionManager.sessionValidationScheduler:設置會話驗證調度器,默認就是使用ExecutorServiceSessionValidationScheduler。

Shiro也提供了使用Quartz會話驗證調度器:

1. sessionValidationScheduler=org.apache.shiro.session.mgt.quartz.QuartzSessionValidationScheduler 

2. sessionValidationScheduler.sessionValidationInterval = 3600000

3. sessionValidationScheduler.sessionManager=$sessionManager  

使用時須要導入shiro-quartz依賴:

1. <dependency> 

2.      <groupId>org.apache.shiro</groupId> 

3.      <artifactId>shiro-quartz</artifactId> 

4.      <version>1.2.2</version> 

5. </dependency> 

如上會話驗證調度器實現都是直接調用AbstractValidatingSessionManager 的validateSessions方法進行驗證,其直接調用SessionDAO的getActiveSessions方法獲取全部會話進行驗證,若是會話比較多,會影響性能;能夠考慮如分頁獲取會話並進行驗證,如com.github.zhangkaitao.shiro.chapter10.session.scheduler.MySessionValidationScheduler:

1. //分頁獲取會話並驗證

2. String sql = "select session from sessions limit ?,?"; 

3. int start = 0; //起始記錄

4. int size = 20; //每頁大小

5. List<String> sessionList = jdbcTemplate.queryForList(sql, String.class, start, size); 

6. while(sessionList.size() > 0) { 

7. for(String sessionStr : sessionList) { 

8. try { 

9.       Session session = SerializableUtils.deserialize(sessionStr); 

10.       Method validateMethod =  

11.         ReflectionUtils.findMethod(AbstractValidatingSessionManager.class,  

12. "validate", Session.class, SessionKey.class); 

13.       validateMethod.setAccessible(true); 

14.       ReflectionUtils.invokeMethod(validateMethod,  

15.         sessionManager, session, new DefaultSessionKey(session.getId())); 

16.     } catch (Exception e) { 

17. //ignore

18.     } 

19.   } 

20.  start = start + size; 

21.   sessionList = jdbcTemplate.queryForList(sql, String.class, start, size); 

22. }  

其直接改造自ExecutorServiceSessionValidationScheduler,如上代碼是驗證的核心代碼,能夠根據本身的需求改造此驗證調度器器;ini的配置和以前的相似。

若是在會話過時時不想刪除過時的會話,能夠經過以下ini配置進行設置:

1. sessionManager.deleteInvalidSessions=false

sessionManager.deleteInvalidSessions=false

默認是開啓的,在會話過時後會調用SessionDAO的delete方法刪除會話:如會話時持久化存儲的,能夠調用此方法進行刪除。

若是是在獲取會話時驗證了會話已過時,將拋出InvalidSessionException;所以須要捕獲這個異常並跳轉到相應的頁面告訴用戶會話已過時,讓其從新登陸,如能夠在web.xml配置相應的錯誤頁面:

1. <error-page> 

2.     <exception-type>org.apache.shiro.session.InvalidSessionException</exception-type> 

3.     <location>/invalidSession.jsp</location> 

4. </error-page> 

 

sessionFactory

sessionFactory是建立會話的工廠,根據相應的Subject上下文信息來建立會話;默認提供了SimpleSessionFactory用來建立SimpleSession會話。

首先自定義一個Session:

1. public class OnlineSession extends SimpleSession { 

2. public static enum OnlineStatus { 

3.         on_line("在線"), hidden("隱身"), force_logout("強制退出"); 

4. private final String info; 

5. private OnlineStatus(String info) { 

6. this.info = info; 

7.         } 

8. public String getInfo() { 

9. return info; 

10.         } 

11.     } 

12. private String userAgent; //用戶瀏覽器類型

13. private OnlineStatus status = OnlineStatus.on_line; //在線狀態

14. private String systemHost; //用戶登陸時系統IP

15. //省略其餘

16. }  

OnlineSession用於保存當前登陸用戶的在線狀態,支持如離線等狀態的控制。

接着自定義SessionFactory:

1. public class OnlineSessionFactory implements SessionFactory { 

2.

3. @Override

4. public Session createSession(SessionContext initData) { 

5.         OnlineSession session = new OnlineSession(); 

6. if (initData != null && initData instanceof WebSessionContext) { 

7.             WebSessionContext sessionContext = (WebSessionContext) initData; 

8.             HttpServletRequest request = (HttpServletRequest) sessionContext.getServletRequest(); 

9. if (request != null) { 

10.                 session.setHost(IpUtils.getIpAddr(request)); 

11.                 session.setUserAgent(request.getHeader("User-Agent")); 

12.                 session.setSystemHost(request.getLocalAddr() + ":" + request.getLocalPort()); 

13.             } 

14.         } 

15. return session; 

16.     } 

17. }  

根據會話上下文建立相應的OnlineSession。

最後在shiro-web.ini配置文件中配置:

sessionFactory=org.apache.shiro.session.mgt.OnlineSessionFactory 

sessionManager.sessionFactory=$sessionFactory  sessionFactory=org.apache.shiro.session.mgt.OnlineSessionFactory

sessionManager.sessionFactory=$sessionFactory

相關文章
相關標籤/搜索