Shiro學習(10)Session管理

來源:互聯網
上載者:User

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

會話
所謂會話,即使用者訪問應用時持續連線關係,在多次互動中應用能夠識別出當前訪問的使用者是誰,且可以在多次互動中儲存一些資料。如訪問一些網站時登入成功後,網站可以記住使用者,且在退出之前都可以識別目前使用者是誰。

Shiro的會話支援不僅可以在普通的JavaSE應用中使用,也可以在JavaEE應用中使用,如web應用。且使用方式是一致的。
Java代碼

login("classpath:shiro.ini", "zhang", "123");  Subject subject = SecurityUtils.getSubject();  Session session = subject.getSession(); 

登入成功後使用Subject.getSession()即可擷取會話;其等價於Subject.getSession(true),即如果當前沒有建立Session對象會建立一個;另外Subject.getSession(false),如果當前沒有建立Session則返回null(不過預設情況下如果啟用會話儲存功能的話在建立Subject時會主動建立一個Session)。

Java代碼

session.getId();  

擷取當前會話的唯一標識。

Java代碼

session.getHost();  

擷取當前Subject的主機地址,該地址是通過HostAuthenticationToken.getHost()提供的。

Java代碼

session.getTimeout();  session.setTimeout(毫秒);  

擷取/設定當前Session的到期時間;如果不設定預設是會話管理器的全域到期時間。

Java代碼

session.getStartTimestamp();  session.getLastAccessTime();  

擷取會話的啟動時間及最後訪問時間;如果是JavaSE應用需要自己定期調用session.touch()去更新最後訪問時間;如果是Web應用,每次進入ShiroFilter都會自動調用session.touch()來更新最後訪問時間。

Java代碼

session.touch();  session.stop(); 

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

Java代碼

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提供了如下介面:

Java代碼

Session start(SessionContext context); //啟動會話  Session getSession(SessionKey key) throws SessionException; //根據會話Key擷取會話

另外用於Web環境的WebSessionManager又提供了如下介面:

Java代碼

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

Shiro還提供了ValidatingSessionManager用於驗資並到期會話:
Java代碼

void validateSessions();//驗證所有會話是否到期  

Shiro提供了三個預設實現:
DefaultSessionManager:DefaultSecurityManager使用的預設實現,用於JavaSE環境;
ServletContainerSessionManager:DefaultWebSecurityManager使用的預設實現,用於Web環境,其直接使用Servlet容器的會話;
DefaultWebSessionManager:用於Web環境的實現,可以替代ServletContainerSessionManager,自己維護著會話,直接廢棄了Servlet容器的會話管理。

替換SecurityManager預設的SessionManager可以在ini中配置(shiro.ini):

Java代碼
[main]

sessionManager=org.apache.shiro.session.mgt.DefaultSessionManager  securityManager.sessionManager=$sessionManager 

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

Java代碼

[main]  sessionManager=org.apache.shiro.web.session.mgt.ServletContainerSessionManager  securityManager.sessionManager=$sessionManager  

另外可以設定會話的全域到期時間(毫秒為單位),預設30分鐘:

Java代碼

sessionManager. globalSessionTimeout=1800000   

預設情況下globalSessionTimeout將應用給所有Session。可以單獨設定每個Session的timeout屬性來為每個Session設定其逾時時間。

另外如果使用ServletContainerSessionManager進行會話管理,Session的逾時依賴於底層Servlet容器的逾時時間,可以在web.xml中配置其會話的逾時時間(分鐘為單位):
Java代碼

<session-config>    <session-timeout>30</session-timeout>  </session-config>  

在Servlet容器中,預設使用JSESSIONID Cookie維護會話,且會話預設是跟容器綁定的;在某些情況下可能需要使用自己的會話機制,此時我們可以使用DefaultWebSessionManager來維護會話:
Java代碼

sessionIdCookie=org.apache.shiro.web.servlet.SimpleCookie  sessionManager=org.apache.shiro.web.session.mgt.DefaultWebSessionManager  sessionIdCookie.name=sid  #sessionIdCookie.domain=sishuok.com  #sessionIdCookie.path=  sessionIdCookie.maxAge=1800  sessionIdCookie.httpOnly=true  sessionManager.sessionIdCookie=$sessionIdCookie  sessionManager.sessionIdCookieEnabled=true  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模板。

會話監聽器
會話監聽器用於監聽會話建立、到期及停止事件:
Java代碼

public class MySessionListener1 implements SessionListener {      @Override      public void onStart(Session session) {//會話建立時觸發          System.out.println("會話建立:" + session.getId());      }      @Override      public void onExpiration(Session session) {//會話到期時觸發          System.out.println("會話到期:" + session.getId());      }      @Override      public void onStop(Session session) {//退出/會話到期時觸發          System.out.println("會話停止:" + session.getId());      }    }  

如果只想監聽某一個事件,可以繼承SessionListenerAdapter實現:
Java代碼

public class MySessionListener2 extends SessionListenerAdapter {      @Override      public void onStart(Session session) {          System.out.println("會話建立:" + session.getId());      }  }  

在shiro-web.ini設定檔中可以進行如下配置設定會話監聽器:
Java代碼

sessionListener1=com.github.zhangkaitao.shiro.chapter10.web.listener.MySessionListener1  sessionListener2=com.github.zhangkaitao.shiro.chapter10.web.listener.MySessionListener2  sessionManager.sessionListeners=$sessionListener1,$sessionListener2  

會話儲存/持久化
Shiro提供SessionDAO用於會話的CRUD,即DAO(Data Access Object)模式實現:
Java代碼

//如DefaultSessionManager在建立完session後會調用該方法;如儲存到關聯式資料庫/檔案系統/NoSQL資料庫;即可以實現會話的持久化;返回會話ID;主要此處返回的ID.equals(session.getId());  Serializable create(Session session);  //根據會話ID擷取會話  Session readSession(Serializable sessionId) throws UnknownSessionException;  //更新會話;如更新會話最後訪問時間/停止會話/設定逾時時間/設定移除屬性等會調用  void update(Session session) throws UnknownSessionException;  //刪除會話;當會話到期/會話停止(如使用者退出時)會調用  void delete(Session session);  //擷取當前所有活躍使用者,如果使用者量多此方法影響效能  Collection<Session> getActiveSessions();   

Shiro內嵌了如下SessionDAO實現:

AbstractSessionDAO提供了SessionDAO的基礎實現,如產生會話ID等;CachingSessionDAO提供了對開發人員透明的會話緩衝的功能,只需要設定相應的CacheManager即可;MemorySessionDAO直接在記憶體中進行會話維護;而EnterpriseCacheSessionDAO提供了緩衝功能的會話維護,預設情況下使用MapCache實現,內部使用ConcurrentHashMap儲存緩衝的會話。

可以通過如下配置設定SessionDAO:
Java代碼

sessionDAO=org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO  sessionManager.sessionDAO=$sessionDAO   

Shiro提供了使用Ehcache進行會話儲存,Ehcache可以配合TerraCotta實現容器無關的分布式叢集。

首先在pom.xml裡添加如下依賴:
Java代碼

<dependency>      <groupId>org.apache.shiro</groupId>      <artifactId>shiro-ehcache</artifactId>      <version>1.2.2</version>  </dependency>   

接著配置shiro-web.ini檔案:
Java代碼

sessionDAO=org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO  sessionDAO. activeSessionsCacheName=shiro-activeSessionCache  sessionManager.sessionDAO=$sessionDAO  cacheManager = org.apache.shiro.cache.ehcache.EhCacheManager  cacheManager.cacheManagerConfigFile=classpath:ehcache.xml  securityManager.cacheManager = $cacheManager   

sessionDAO. activeSessionsCacheName:設定Session緩衝名字,預設就是shiro-activeSessionCache;
cacheManager:緩衝管理器,用於管理緩衝的,此處使用Ehcache實現;
cacheManager.cacheManagerConfigFile:設定ehcache緩衝的設定檔;
securityManager.cacheManager:設定SecurityManager的cacheManager,會自動化佈建實現了CacheManagerAware介面的相應對象,如SessionDAO的cacheManager;

然後配置ehcache.xml:
Java代碼 收藏代碼

<cache name="shiro-activeSessionCache"         maxEntriesLocalHeap="10000"         overflowToDisk="false"         eternal="false"         diskPersistent="false"         timeToLiveSeconds="0"         timeToIdleSeconds="0"         statistics="true"/>   

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

另外可以通過如下ini配置設定會話ID產生器:
Java代碼

sessionIdGenerator=org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator  sessionDAO.sessionIdGenerator=$sessionIdGenerator   

用於產生會話ID,預設就是JavaUuidSessionIdGenerator,使用java.util.UUID產生。

如果自訂實現SessionDAO,繼承CachingSessionDAO即可:
Java代碼

public class MySessionDAO extends CachingSessionDAO {      private JdbcTemplate jdbcTemplate = JdbcTemplateUtils.jdbcTemplate();       protected Serializable doCreate(Session session) {          Serializable sessionId = generateSessionId(session);          assignSessionId(session, sessionId);          String sql = "insert into sessions(id, session) values(?,?)";          jdbcTemplate.update(sql, sessionId, SerializableUtils.serialize(session));          return session.getId();      }  protected void doUpdate(Session session) {      if(session instanceof ValidatingSession && !((ValidatingSession)session).isValid()) {          return; //如果會話到期/停止 沒必要再更新了      }          String sql = "update sessions set session=? where id=?";          jdbcTemplate.update(sql, SerializableUtils.serialize(session), session.getId());      }      protected void doDelete(Session session) {          String sql = "delete from sessions where id=?";          jdbcTemplate.update(sql, session.getId());      }      protected Session doReadSession(Serializable sessionId) {          String sql = "select session from sessions where id=?";          List<String> sessionStrList = jdbcTemplate.queryForList(sql, String.class, sessionId);          if(sessionStrList.size() == 0) return null;          return SerializableUtils.deserialize(sessionStrList.get(0));      }  }   

doCreate/doUpdate/doDelete/doReadSession分別代表建立/修改/刪除/讀取會話;此處通過把會話序列化後儲存到資料庫實現;接著在shiro-web.ini中配置:
Java代碼

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

其他設定和之前一樣,因為繼承了CachingSessionDAO;所有在讀取時會先查緩衝中是否存在,如果找不到才到資料庫中尋找。

會話驗證
Shiro提供了會話驗證調度器,用於週期性驗證會話是否已到期,如果到期將停止會話;出於效能考慮,一般情況下都是擷取會話時來驗證會話是否到期並停止會話的;但是如在web環境中,如果使用者不主動退出是不知道會話是否到期的,因此需要週期性檢測會話是否到期,Shiro提供了會話驗證調度器SessionValidationScheduler來做這件事情。

可以通過如下ini配置開啟會話驗證:
Java代碼

sessionValidationScheduler=org.apache.shiro.session.mgt.ExecutorServiceSessionValidationScheduler  sessionValidationScheduler.interval = 3600000  sessionValidationScheduler.sessionManager=$sessionManager  sessionManager.globalSessionTimeout=1800000  sessionManager.sessionValidationSchedulerEnabled=true  sessionManager.sessionValidationScheduler=$sessionValidationScheduler   

sessionValidationScheduler:會話驗證調度器,sessionManager預設就是使用ExecutorServiceSessionValidationScheduler,其使用JDK的ScheduledExecutorService進行定期調度並驗證會話是否到期;
sessionValidationScheduler.interval:設定調度時間間隔,單位毫秒,預設就是1小時;
sessionValidationScheduler.sessionManager:設定會話驗證調度器進行會話驗證時的會話管理器;
sessionManager.globalSessionTimeout:設定全域會話逾時時間,預設30分鐘,即如果30分鐘內沒有訪問會話將到期;
sessionManager.sessionValidationSchedulerEnabled:是否開啟會話驗證器,預設是開啟的;
sessionManager.sessionValidationScheduler:設定會話驗證調度器,預設就是使用ExecutorServiceSessionValidationScheduler。

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

sessionValidationScheduler=org.apache.shiro.session.mgt.quartz.QuartzSessionValidationScheduler  sessionValidationScheduler.sessionValidationInterval = 3600000  sessionValidationScheduler.sessionManager=$sessionManager   

使用時需要匯入shiro-quartz依賴:
Java代碼

<dependency>       <groupId>org.apache.shiro</groupId>       <artifactId>shiro-quartz</artifactId>       <version>1.2.2</version>  </dependency>  

如上會話驗證調度器實現都是直接調用AbstractValidatingSessionManager 的validateSessions方法進行驗證,其直接調用SessionDAO的getActiveSessions方法擷取所有會話進行驗證,如果會話比較多,會影響效能;可以考慮如分頁擷取會話並進行驗證,如com.github.zhangkaitao.shiro.chapter10.session.scheduler.MySessionValidationScheduler:
Java代碼

//分頁擷取會話並驗證  String sql = "select session from sessions limit ?,?";  int start = 0; //起始記錄  int size = 20; //每頁大小  List<String> sessionList = jdbcTemplate.queryForList(sql, String.class, start, size);  while(sessionList.size() > 0) {    for(String sessionStr : sessionList) {      try {        Session session = SerializableUtils.deserialize(sessionStr);        Method validateMethod =           ReflectionUtils.findMethod(AbstractValidatingSessionManager.class,               "validate", Session.class, SessionKey.class);        validateMethod.setAccessible(true);        ReflectionUtils.invokeMethod(validateMethod,           sessionManager, session, new DefaultSessionKey(session.getId()));      } catch (Exception e) {          //ignore      }    }   start = start + size;    sessionList = jdbcTemplate.queryForList(sql, String.class, start, size);  }   

其直接改造自ExecutorServiceSessionValidationScheduler,如上代碼是驗證的核心代碼,可以根據自己的需求改造此驗證調度器器;ini的配置和之前的類似。

如果在會話到期時不想刪除到期的會話,可以通過如下ini配置進行設定:
Java代碼

sessionManager.deleteInvalidSessions=false  

預設是開啟的,在會話到期後會調用SessionDAO的delete方法刪除會話:如會話時持久化儲存的,可以調用此方法進行刪除。

如果是在擷取會話時驗證了會話已到期,將拋出InvalidSessionException;因此需要捕獲這個異常並跳轉到相應的頁面告訴使用者會話已到期,讓其重新登入,如可以在web.xml配置相應的錯誤頁面:
Java代碼

<error-page>      <exception-type>org.apache.shiro.session.InvalidSessionException</exception-type>      <location>/invalidSession.jsp</location>  </error-page>  

sessionFactory
sessionFactory是建立會話的工廠,根據相應的Subject上下文資訊來建立會話;預設提供了SimpleSessionFactory用來建立SimpleSession會話。

首先自訂一個Session:
Java代碼

public class OnlineSession extends SimpleSession {      public static enum OnlineStatus {          on_line("線上"), hidden("隱藏"), force_logout("強制退出");          private final String info;          private OnlineStatus(String info) {              this.info = info;          }          public String getInfo() {              return info;          }      }      private String userAgent; //使用者瀏覽器類型      private OnlineStatus status = OnlineStatus.on_line; //線上狀態      private String systemHost; //使用者登入時系統IP      //省略其他  }   

OnlineSession用於儲存當前登入使用者的線上狀態,支援如離線等狀態的控制。

接著自訂SessionFactory:
Java代碼

public class OnlineSessionFactory implements SessionFactory {      @Override      public Session createSession(SessionContext initData) {          OnlineSession session = new OnlineSession();          if (initData != null && initData instanceof WebSessionContext) {              WebSessionContext sessionContext = (WebSessionContext) initData;              HttpServletRequest request = (HttpServletRequest) sessionContext.getServletRequest();              if (request != null) {                  session.setHost(IpUtils.getIpAddr(request));                  session.setUserAgent(request.getHeader("User-Agent"));                  session.setSystemHost(request.getLocalAddr() + ":" + request.getLocalPort());              }          }          return session;      }  }   

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

最後在shiro-web.ini設定檔中配置:
Java代碼

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

更多請參考https://github.com/zhangkaitao/es/tree/master/web/src/main/java/org/apache/shiro中的相關代碼。

樣本原始碼:https://github.com/zhangkaitao/shiro-example

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.