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. }
在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