shiro 之 Session Management

来源:互联网 发布:独立时代 知乎 编辑:程序博客网 时间:2024/06/06 03:09

shiro 之 Session Management

本节我们将学习一下 Shiro 的 Session Management 即 Shiro 提供的核心功能之一: 会话管理。


概念

  • Session

    • 通常而言会话指的是用户访问应用时保持的连接关系,在多次交互中应用能够识别出当前访问的用户是谁,且可以在多次交互中保存一些数据。如访问一些网站时登录成功后,网站可以记住用户,且在退出之前都可以识别当前用户是谁。
  • Shiro Session Management

    • 官方介绍

      • The SessionManager knows how to create and manage user Session lifecycles to provide a robust Session experience for users in all environments. This is a unique feature in the world of security frameworks - Shiro has the ability to natively manage user Sessions in any environment, even if there is no Web/Servlet or EJB container available. By default, Shiro will use an existing session mechanism if available, (e.g. Servlet Container), but if there isn’t one, such as in a standalone application or non-web environment, it will use its built-in enterprise session management to offer the same programming experience. The SessionDAO exists to allow any datasource to be used to persist sessions.

        SessionDAO (org.apache.shiro.session.mgt.eis.SessionDAO)
        The SessionDAO performs Session persistence (CRUD) operations on behalf of the SessionManager. This allows any data store to be plugged in to the Session Management infrastructure.

    • 通常而言

      • Shiro 提供了完整的企业级会话管理功能,不依赖于底层容器(如web容器tomcat),不管 JavaSE 还是 JavaEE 环境都可以使用,提供了会话管理、会话事件监听、会话存储/ 持久化、容器无关的集群、失效/过期支持、对Web 的透明支持、SSO 单点登录的支持等特性。

实例

下面我们将通过实例来学习一下 Shiro Session Management & Session Transaction Listener & Web support 等。我们延用之前的例子。Session Manage在 Context中的配置。

  • 配置 sessionIdGenerator

    <!-- Shiro SessionIdGenerator -->    <bean id="sessionIdGenerator" class="org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator"></bean>
    public class JavaUuidSessionIdGenerator implements SessionIdGenerator {    /**     * Ignores the method argument and simply returns     * {@code UUID}.{@link java.util.UUID#randomUUID() randomUUID()}.{@code toString()}.     *     * @param session the {@link Session} instance to which the ID will be applied.     * @return the String value of the JDK's next {@link UUID#randomUUID() randomUUID()}.     */    public Serializable generateId(Session session) {        return UUID.randomUUID().toString();    }}
  • 配置sessionDao(此为源生sessionDao)

    <!-- Shiro SessionDao -->    <bean id="sessionDao" class="org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO">        <property name="activeSessionsCacheName" value="shiro-activeSessionCache"></property>        <property name="sessionIdGenerator" ref="sessionIdGenerator"></property>        <property name="cacheManager" ref="cacheManager"></property>    </bean>
  • 配置Cookie数据模型

    <!-- 配置需要向Cookie中保存数据的配置模版 -->     <bean id="sessionIdCookie" class="org.apache.shiro.web.servlet.SimpleCookie">         <!-- 在Tomcat运行下默认使用的Cookie的名字为JSESSIONID --> <constructor-arg value="mldn-session-id"/>         <!-- 保证该系统不会受到跨域的脚本操作供给 -->         <property name="httpOnly" value="true"/>         <!-- 定义Cookie的过期时间,单位为秒,如果设置为-1表示浏览器关闭,则Cookie消失 -->         <property name="maxAge" value="-1"/>        <!-- 设置Cookie Name 默认的Name=JSESSIONID -->         <property name="name" value="JSessionId"></property>        <!-- 设置Cookie Domain 默认空,即当前访问的域名-->        <!-- <property name="domain" value="sstps.com"></property> -->        <!-- 设置Cookie的路径,默认空,即存储在域名根下  -->       <!--  <property name="path" value="cookies"></property> -->    </bean>
  • 配置 QuartzSessionValidationScheduler

     <!-- 配置session的定时验证检测程序类,以让无效的session释放 -->    <bean id="sessionValidationScheduler"        class="org.apache.shiro.session.mgt.quartz.QuartzSessionValidationScheduler">        <!-- 设置session的失效扫描间隔,单位为毫秒 -->        <property name="sessionValidationInterval" value="100000"/>        <!-- 随后还需要定义有一个会话管理器的程序类的引用 -->        <property name="sessionManager" ref="sessionManager"/>    </bean>
  • 引入 shiro-quartz 依赖

    <!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-quartz --><dependency>    <groupId>org.apache.shiro</groupId>    <artifactId>shiro-quartz</artifactId>    <version>1.2.3</version></dependency><dependency>    <groupId>commons-collections</groupId>    <artifactId>commons-collections</artifactId>    <version>3.2.2</version></dependency>
  • 配置Session Management 将以上Bean 交由 sessionManagement 统一管理

    <!-- 定义会话管理器的操作 --><bean id="sessionManager"    class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">    <!-- 定义的是全局的session会话超时时间,此操作会覆盖web.xml文件中的超时时间配置 -->    <property name="globalSessionTimeout" value="1000000"/>    <!-- 删除所有无效的Session对象,此时的session被保存在了内存里面 -->    <property name="deleteInvalidSessions" value="true"/>    <!-- 定义要使用的无效的Session定时调度器 -->    <property name="sessionValidationScheduler" ref="sessionValidationScheduler"/>    <!-- 需要让此session可以使用该定时调度器进行检测 -->    <property name="sessionValidationSchedulerEnabled" value="true"/>    <!-- 定义Session可以进行序列化的工具类 -->    <property name="sessionDAO" ref="sessionDao"/>    <!-- 所有的session一定要将id设置到Cookie之中,需要提供有Cookie的操作模版 -->    <property name="sessionIdCookie" ref="sessionIdCookie"/>    <!-- 定义sessionIdCookie模版可以进行操作的启用 -->    <property name="sessionIdCookieEnabled" value="true"/></bean> 
  • 将 sessionManagement 交由 SecurityManagement 统一管理

    <!-- 1.配置SecurityManager --><bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">    <property name="cacheManager" ref="cacheManager" />    <!-- Shiro 多Realm认证策略 -->    <property name="authenticator" ref="authenticator"></property>    <!-- Session Management -->    <property name="sessionManager" ref="sessionManager"></property>    <!-- 多Realm热或者能 -->    <property name="realms">        <list>            <ref bean="shiroRealm"></ref>            <ref bean="myRealm"></ref>        </list>    </property></bean>
  • Lo4j 配置

    • log4j.properties

      log4j.rootLogger=DEBUG,CONSOLE,Alog4j.addivity.org.apache=falselog4j.appender.CONSOLE=org.apache.log4j.ConsoleAppenderlog4j.appender.CONSOLE.Threshold=DEBUGlog4j.appender.CONSOLE.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} -%-4r [%t] %-5p  %x - %m%nlog4j.appender.CONSOLE.Target=System.outlog4j.appender.CONSOLE.Encoding=gbklog4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayoutlog4j.appender.A=org.apache.log4j.DailyRollingFileAppender  log4j.appender.A.File=${catalina.home}/logs/FH_log/PurePro_log4j.appender.A.DatePattern=yyyy-MM-dd'.log'log4j.appender.A.layout=org.apache.log4j.PatternLayout  log4j.appender.A.layout.ConversionPattern=[FH_sys]  %d{yyyy-MM-dd HH:mm:ss} %5p %c{1}:%L : %m%n
    • log4j.xml

      <?xml version="1.0" encoding="UTF-8"?><!DOCTYPE log4j:configuration PUBLIC "-//APACHE//DTD LOG4J 1.2//EN" "log4j.dtd"><log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">    <!-- Appenders -->    <appender name="console" class="org.apache.log4j.ConsoleAppender">        <param name="Target" value="System.out" />        <layout class="org.apache.log4j.PatternLayout">            <param name="ConversionPattern" value="%d{yyyy HH:mm:ss} %-5p %c - %m%n" />        </layout>    </appender>    <!-- Application Loggers -->    <logger name="com.shiro.example">        <level value="info" />    </logger>    <!-- 3rdparty Loggers -->    <logger name="org.springframework.core">        <level value="info" />    </logger>       <logger name="org.springframework.beans">        <level value="info" />    </logger>    <logger name="org.springframework.context">        <level value="info" />    </logger>    <logger name="org.springframework.web">        <level value="info" />    </logger>    <logger name="org.springframework.jdbc">        <level value="info" />    </logger>    <logger name="org.mybatis.spring">        <level value="info" />    </logger>    <logger name="java.sql">        <level value="info" />    </logger>    <!-- Root Logger -->    <root>        <priority value="info" />        <appender-ref ref="console" />    </root></log4j:configuration>
  • 成功运行Log 日志

    这里写图片描述


分析&小结

  • Session API

    • Get Shiro Session

      Session session = SecurityUtils.getSubject().getSession();
    • Shiro Session API

      • Subject.getSession() 即可获取会话;其等价于 Subject.getSession(true),即如果当前没有创建 Session 对象会创建一个;Subject.getSession(false),如果当前没有创建 Session 则返回null

      • session.getId():获取当前会话的唯一标识

      • session.getHost():获取当前Subject的主机地址

      • session.getTimeout() & session.setTimeout(毫秒):获取/设置当前Session的过期时间

      • session.getStartTimestamp() & session.getLastAccessTime(): 获取会话的启动时间及最后访问时间;如果是 JavaSE 应用需要自己定期调用 session.touch() 去更新最后访问时间;如果是 Web 应用,每次进入 ShiroFilter 都会自动调用 session.touch() 来更新最后访问时间

      • session.touch() & session.stop():更新会话最后访问时间及销毁会话;当Subject.logout()时会自动调用 stop 方法来销毁会话。如果在web中,调用 HttpSession. invalidate() 也会自动调用Shiro Session.stop 方法进行销毁Shiro 的会话

      • session.setAttribute(key, val) & session.getAttribute(key) &session.removeAttribute(key):设置/获取/删除会话属性;在整个会话范围内都可以对这些属性进行操作

  • SessionListener:会话监听器用于监听会话创建、过期及停止事件:

    • Fork Mutl-SessionListener(监听所有事件)
      通过实现SessionListener 重写各个方法即可实现SessionListener 自定义

      public class MySessionListener implements SessionListener {      @Override      public void onStart(Session session) {//会话创建时触发          System.out.println("createSession:" + session.getId());      }      @Override      public void onExpiration(Session session) {//会话过期时触发          System.out.println("dropSession:" + session.getId());      }      @Override      public void onStop(Session session) {//退出/会话过期时触发          System.out.println("stopSession:" + session.getId());      }    }  
    • Fork Single SessionListener
      如果只想监听某一个事件,可以继承SessionListenerAdapter实现

      public class MySessionListener extends SessionListenerAdapter {      @Override      public void onStart(Session session) {          System.out.println("createSession:" + session.getId());      }  }  

  • SessionValidationScheduler

    Shiro提供了会话验证调度器,用于定期的验证会话是否已过期,如果过期将停止会话;出于性能考虑,一般情况下都是获取会话时来验证会话是否过期并停止会话的;但是如在web环境中,如果用户不主动退出是不知道会话是否过期的,因此需要定期的检测会话是否过期,Shiro提供了会话验证调度器SessionValidationScheduler来做这件事情。

    • sessionValidationScheduler
      会话验证调度器,sessionManager默认就是使用 ExecutorServiceSessionValidationScheduler,其使用JDK的ScheduledExecutorService进行定期调度并验证会话是否过期。而在本届例子例子总我们使用的是 org.apache.shiro.session.mgt.quartz.QuartzSessionValidationScheduler

    • sessionValidationScheduler.interval:设置session的失效扫描间隔,单位为毫秒

    • sessionValidationScheduler.sessionManager:设置会话验证调度器进行会话验证时的会话管理器;

    • 需要导入 shiro-quartz 依赖

  • Session SimpleCookie

    在Servlet容器中,默认使用JSESSIONID Cookie维护会话,且会话默认是跟容器绑定的;在某些情况下可能需要使用自己的会话机制,此时我们可以使用DefaultWebSessionManager来维护会话,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。


  • SessionDao

    • Shiro提供SessionDAO用于会话的CRUD,即DAO(Data Access Object)模式实现

      这里写图片描述

    • SessionDao:顶层实现类(源生)

      • AbstractSessionDAO:提供了 SessionDAO 的基础实现, 如生成会话ID等

        • CachingSessionDAO:提供了对开发者透明的会话缓存的功能,需要设置相应的 CacheManager

          • EnterpriseCacheSessionDAO:提供了缓存功能的会话维护,默认情况下使用 MapCache 实现,内部使用ConcurrentHashMap 保存缓存的会话。
        • MemorySessionDAO:直接在内存中进行会话维护

    • 源生SessionDao

      源生的 SessionDao Shiro 默认使用的是EnterpriseCacheSessionDAO

      public class EnterpriseCacheSessionDAO extends CachingSessionDAO {    public EnterpriseCacheSessionDAO() {        setCacheManager(new AbstractCacheManager() {            @Override            protected Cache<Serializable, Session> createCache(String name) throws CacheException {                return new MapCache<Serializable, Session>(name, new ConcurrentHashMap<Serializable, Session>());            }        });    }    protected Serializable doCreate(Session session) {        Serializable sessionId = generateSessionId(session);        assignSessionId(session, sessionId);        return sessionId;    }    protected Session doReadSession(Serializable sessionId) {        return null; //should never execute because this implementation relies on parent class to access cache, which        //is where all sessions reside - it is the cache implementation that determines if the        //cache is memory only or disk-persistent, etc.    }    protected void doUpdate(Session session) {        //does nothing - parent class persists to cache.    }    protected void doDelete(Session session) {        //does nothing - parent class removes from cache.    }}

      以上可代码可知

      • EnterpriseCacheSessionDAO 继承了 CachingSessionDAO

      • EnterpriseCacheSessionDAO 中的 Session CURD 方法在 父类 CachingSessionDAO 中有实现。
        因而在使用默认的 EnterpriseCacheSessionDAO 时,我们可以不写任何的实现方法,直接使用父类CachingSessionDAO 的高级实现即可

      • CachingSessionDAO 需要Session Cache 的支持,关于Shiro Cache 马上将在下一篇学习。

    • 自定义SessionDao

      • SessionDao.java

        package com.fh.util.wechat;import java.io.Serializable;import java.util.List;import org.apache.shiro.session.Session;import org.apache.shiro.session.mgt.ValidatingSession;import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.jdbc.core.JdbcTemplate;public class MySessionDao extends EnterpriseCacheSessionDAO {    @Autowired    private JdbcTemplate jdbcTemplate = null;    //SerializableUtils.serialize(session)    protected Serializable doCreate(Session session) {        Serializable sessionId = generateSessionId(session);        assignSessionId(session, sessionId);        String sql = "insert into shiro-session (id, session) values(?,?)";        jdbcTemplate.update(sql, sessionId,"456");        return session.getId();    }    protected Session doReadSession(Serializable sessionId) {        String sql = "select shiro-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));    }    protected void doUpdate(Session session) {        if (session instanceof ValidatingSession                && !((ValidatingSession) session).isValid()) {            return;         }        String sql = "update shiro-session set session=? where id=?";        jdbcTemplate.update(sql, SerializableUtils.serialize(session),                session.getId());    }    protected void doDelete(Session session) {        String sql = "delete from shiro-session where id=?";        jdbcTemplate.update(sql, session.getId());    }}
      • SerializableUtils.java

        package com.fh.util.wechat;import org.apache.shiro.codec.Base64;import org.apache.shiro.session.Session;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;public class SerializableUtils {    public static String serialize(Session session) {        try {            ByteArrayOutputStream bos = new ByteArrayOutputStream();            ObjectOutputStream oos = new ObjectOutputStream(bos);            oos.writeObject(session);            return Base64.encodeToString(bos.toByteArray());        } catch (Exception e) {            throw new RuntimeException("serialize session error", e);        }    }    public static Session deserialize(String sessionStr) {        try {            ByteArrayInputStream bis = new ByteArrayInputStream(Base64.decode(sessionStr));            ObjectInputStream ois = new ObjectInputStream(bis);            return (Session) ois.readObject();        } catch (Exception e) {            throw new RuntimeException("deserialize session error", e);        }    }}
    • SessionDao(不论是使用源生的还是自定义)必配的属性

      • sessionDAO. activeSessionsCacheName:设置Session缓存名字,默认就是shiro-activeSessionCache;

      • sessionDao.sessionIdGenerator:指定sessionId 生成器

      • sessionDao.cacheManager:指定sessionDao使用的缓存,既可以是Shiro 自带的缓存,也可以是Redis等缓存。


  • sessionManager

    会话管理器管理着应用中所有Subject的会话的创建、维护、删除、失效、验证等工作。是Shiro的核心组件,顶层组件SecurityManager直接继承了SessionManager,且提供了SessionsSecurityManager实现直接把会话管理委托给相应的SessionManager,DefaultSecurityManager及DefaultWebSecurityManager默认SecurityManager都继承了SessionsSecurityManager。

    这里写图片描述

  • Shiro 提供了三个sessionManager的实现

    • DefaultSessionManager:DefaultSecurityManager使用的默认实现,用于JavaSE环境;

    • ServletContainerSessionManager:DefaultWebSecurityManager使用的默认实现,用于Web环境,其直接使用Servlet容器的会话;

    • DefaultWebSessionManager:用于Web环境的实现,可以替代ServletContainerSessionManager,自己维护着会话,直接废弃了Servlet容器的会话管理。

  • Shiro sessionManager 常用配置

    • sessionManager.globalSessionTimeout:定义的是全局的session会话超时时间,此操作会覆盖web.xml文件中的超时时间配置

    • sessionManager.deleteInvalidSessions:true。删除所有无效的Session对象,此时的session被保存在了内存里面

    • sessionManager.sessionValidationScheduler: 定义要使用的无效的Session定时调度器

    • sessionManager.sessionValidationSchedulerEnabled:true。需要让此session可以使用该定时调度器进行检测

    • sessionManager.sessionDAO:定义Session可以进行序列化的工具类

    • sessionManager.sessionIdCookie:所有的session一定要将id设置到Cookie之中,需要提供有Cookie的操作模版

    • sessionManager.sessionIdCookieEnabled:true。定义sessionIdCookie模版可以进行操作的启用

原创粉丝点击