Shiro - 会话管理与SessionDao持久化Session

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

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


【1】Shiro Session接口与实现类


这里的Session不再是我们通常使用的javax.servlet.http.HttpSession,而是org.apache.shiro.session.Session。


一个Session是与一段时间内与软件系统交互的单个对象(用户、守护进程等)相关的有状态数据上下文。


该Session旨在由业务层管理,并且可以通过其他层访问,而不绑定到任何给定的客户端技术。这是一个很大的好处对Java系统而言,因为到目前为止,唯一可行的会话机制是{javax .servlet .http.httpsession }或有状态会话EJB,这些应用程序多次不必要地将应用程序耦合到Web或EJB技术。


不过在使用上与httpsession 有相似之处,相关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() 也会自动调用ShiroSession.stop方法进行销毁Shiro的会话

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



Session实现类如下

org.apache.shiro.web.session.HttpServletSession由标准servlet容器javax.servlet.http.HttpSession支持。它不与Shiro的会话相关组件SessionManager、SecurityManager等交互,而是通过与提供的servlet容器 httpsession实例交互来满足所有方法实现。其属性和方法如下:


javax.servlet.http.HttpSession实现类如下:


其中ShiroHttpSession是一个包装类,在底层使用一个Shiro Session替代标准servlet容器javax.servlet.http.HttpSession。这在异类客户机环境中是必需的,在异类客户机环境中,会话既用于业务层,也用于多种客户机技术(Web、Swing、Flash等),因为单独的servlet容器会话不支持此功能。

SessionManager实现类如下:


【2】会话监听器


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

源码如下:

public interface SessionListener {
    /**
     * Notification callback that occurs when the corresponding Session has started.
     *
     * @param session the session that has started.
     */
    void onStart(Session session);
    /**
     * Notification callback that occurs when the corresponding Session has stopped, either programmatically via
     * {@link Session#stop} or automatically upon a subject logging out.
     *
     * @param session the session that has stopped.
     */
    void onStop(Session session);
    /**
     * Notification callback that occurs when the corresponding Session has expired.
     * <p/>
     * <b>Note</b>: this method is almost never called at the exact instant that the {@code Session} expires.  Almost all
     * session management systems, including Shiro's implementations, lazily validate sessions - either when they
     * are accessed or during a regular validation interval.  It would be too resource intensive to monitor every
     * single session instance to know the exact instant it expires.
     * <p/>
     * If you need to perform time-based logic when a session expires, it is best to write it based on the
     * session's {@link org.apache.shiro.session.Session#getLastAccessTime() lastAccessTime} and <em>not</em> the time
     * when this method is called.
     *
     * @param session the session that has expired.
     */
    void onExpiration(Session session);
}


Shiro Session一个重要应用

在Controller通常会使用HttpSession进行操作,那么在Service层为了降低侵入、解耦,我们就可以使用Shiro Session进行操作。


如在Controller放入Session中一个键值对:

   @ResponseBody
    @RequestMapping(value="/test",produces="application/json;charset=utf-8")
    public String  test(HttpSession session) {
      System.out.println("调用方法test");
      session.setAttribute("key", "123456");
      return "success";
    }


在Service使用Shiro Session进行获取:

  @Override
  public List<SysRole> getRoleListByUserId(Long id) {
    // TODO Auto-generated method stub
    Session session = SecurityUtils.getSubject().getSession();
    Object attribute = session.getAttribute("key");
    List<SysRole> roleListByUserId = userServiceDao.getRoleListByUserId(id);
    return roleListByUserId;
  }


【3】SessionDao

SessionDao提供了一种方式,使我们能够将session存入数据库(缓存中)中进行CRUD操作。这有什么意义?当只有一台服务器一个项目的时候通常你不必管理Session,Shiro会自行管理Session。


但是如果有多个服务器同时跑一个项目呢?或者单点登录,不同项目在不同服务器,但是需要实现单点登录功能。这是你就需要在服务器之间共享Session!项目中通常我们使用Redis来实现共享Session。


① SessionDao接口继承图如下:


② 几个实现类


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


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


MemorySessionDAO直接在内存中进行会话维护。


EnterpriseCacheSessionDAO提供了缓存功能的会话维护,默认情况下使用MapCache实现,内部使用ConcurrentHashMap保存缓存的会话。



③ xml配置与自定义MySessionDao

pom文件中关于Shiro依赖如下:

 <!-- shiro 版本为1.4.0 -->
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-core</artifactId>
      <version>${shiro.version}</version>
    </dependency>
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-ehcache</artifactId>
      <version>${shiro.version}</version>
    </dependency>
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-web</artifactId>
      <version>${shiro.version}</version>
    </dependency>
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-spring</artifactId>
      <version>${shiro.version}</version>
    </dependency>
    <dependency> 
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-quartz</artifactId> 
      <version>${shiro.version}</version>
  </dependency>


自定义MySessionDao:

public class MySessionDao extends EnterpriseCacheSessionDAO {
  //这里注入Spring提供的JdbcTemplate
  @Autowired
  private JdbcTemplate jdbcTemplate = null;
  @Override
  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();
  }
  @Override
  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));
  }
  @Override
  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());
  }
  @Override
  protected void doDelete(Session session) {
    String sql = "delete from sessions where id=?";
    jdbcTemplate.update(sql, session.getId());
  }
}

Shiro XML配置如下:

 <!-- 配置需要向Cookie中保存数据的配置模版 --> 
  <bean id="sessionIdCookie" class="org.apache.shiro.web.servlet.SimpleCookie"> 
      <!-- 在Tomcat运行下默认使用的Cookie的名字为JSESSIONID --> 
      <constructor-arg value="shiro-session-id"/> 
      <!-- 保证该系统不会受到跨域的脚本操作供给 --> 
      <property name="httpOnly" value="true"/> 
      <!-- 定义Cookie的过期时间,单位为秒,如果设置为-1表示浏览器关闭,则Cookie消失 --> 
      <property name="maxAge" value="-1"/> 
  </bean>
    <!-- Session ID 生成器-->
  <bean id="sessionIdGenerator"
    class="org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator"/>
  <!-- Session DAO. 继承 EnterpriseCacheSessionDAO -->
  <bean id="sessionDAO"
    class="com.web.maven.shiro.MySessionDao">
    <property name="activeSessionsCacheName" value="shiro-activeSessionCache"/>
    <property name="sessionIdGenerator" ref="sessionIdGenerator"/>
  </bean>
  <!-- 会话管理器-->
  <bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
   <!-- 定义的是全局的session会话超时时间,此操作会覆盖web.xml文件中的超时时间配置 -->
    <property name="globalSessionTimeout" value="1800000"/>
    <!-- 删除所有无效的Session对象,此时的session被保存在了内存里面 -->
    <property name="deleteInvalidSessions" value="true"/>
     <!-- 定义要使用的无效的Session定时调度器 -->
        <property name="sessionValidationScheduler" ref="sessionValidationScheduler"/>
         <!-- 需要让此session可以使用该定时调度器进行检测 -->
        <property name="sessionValidationSchedulerEnabled" value="true"/>
        <!-- 定义Session可以进行操作的DAO -->
    <property name="sessionDAO" ref="sessionDAO"/>
    <!-- 所有的session一定要将id设置到Cookie之中,需要提供有Cookie的操作模版 -->
        <property name="sessionIdCookie" ref="sessionIdCookie"/>
         <!-- 定义sessionIdCookie模版可以进行操作的启用 -->
        <property name="sessionIdCookieEnabled" value="true"/>
        <!-- url sessionId  重写 -->
        <property name="sessionIdUrlRewritingEnabled" value="true"/>
  </bean>
  <!-- 配置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> 
    <!-- 安全管理器 -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <!-- 注入自定义Realm -->
<!--         <property name="realm" ref="customRealm"/> -->
        <!-- 注入缓存管理器 -->
        <property name="cacheManager" ref="cacheManager"/>
        <property name="authenticator" ref="authenticator" />
        <property name="realms">
          <list>
          <ref bean="customRealm"/>
<!--          <ref bean="customRealm2"/> -->
        </list>
        </property>
        <property name="sessionManager" ref="sessionManager" />
    </bean>
    <!-- 认证器 -->
    <bean id="authenticator" 
      class="org.apache.shiro.authc.pam.ModularRealmAuthenticator">
      <property name="authenticationStrategy">
        <bean class="org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy"></bean>
      </property>
    </bean>
    <!-- 缓存管理器 -->
    <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
        <property name="cacheManagerConfigFile" value="classpath:shiro-ehcache.xml"/>
    </bean>
      <!-- 自定义Realm -->
    <bean id="customRealm" class="com.web.maven.shiro.CustomRealm">
        <!-- 将凭证匹配器设置到realm中,realm按照凭证匹配器的要求进行散列 -->
        <property name="credentialsMatcher">
           <bean  class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
              <property name="hashAlgorithmName" value="MD5"/>
              <property name="hashIterations" value="1"/>
            </bean>
        </property>
    </bean>
      <!-- 自定义SecondRealm -->
    <bean id="customRealm2" class="com.web.maven.shiro.CustomRealm2">
        <!-- 将凭证匹配器设置到realm中,realm按照凭证匹配器的要求进行散列 -->
        <property name="credentialsMatcher">
           <bean  class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
              <property name="hashAlgorithmName" value="SHA1"/>
              <property name="hashIterations" value="1"/>
            </bean>
        </property>
    </bean>
    <!-- 配置lifecycleBeanPostProcessor,可以自动的调用配置在spring IOC 容器中shiro bean的生命周期方法。 -->
    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"></bean>
   <!-- 开启Shiro的注解,实现对Controller的方法级权限检查(如@RequiresRoles,@RequiresPermissions),
      需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证  -->   
    <!-- Enable Shiro Annotations for Spring-configured beans. Only run after the lifecycleBeanProcessor has run -->  
  <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor" /> 
  <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
    <property name="securityManager" ref="securityManager" />
  </bean>
    <!-- Shiro过滤器 -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <!-- Shiro的核心安全接口,这个属性是必须的 -->
        <property name="securityManager" ref="securityManager"/>
        <!-- loginUrl认证提交地址,如果没有认证将会请求此地址进行认证,请求此地址将由formAuthenticationFilter进行表单认证 -->
        <property name="loginUrl" value="/login"/>
        <!-- if is Authenticated,then ,rediret to the url  -->
        <property name="successUrl" value="/index"/>
        <!-- has no permission and then redirect to the url  -->
        <property name="unauthorizedUrl" value="/refuse"></property>
       <!--<property name="filters">
            <map>
                  重写 退出过滤器
                <entry key="logout" value-ref="systemLogoutFilter" />
            </map>
        </property>-->
        <!-- Shiro连接约束配置,即过滤链的定义 -->
        <property name="filterChainDefinitions">
            <value>
                <!-- /** = anon所有url都可以匿名访问 -->
                <!-- 对静态资源设置匿名访问 -->
                /test=anon
                /favicon.ico = anon
                /images/** = anon
                /js/** = anon
                /styles/** = anon
                /css/** = anon
                /*.jar = anon
                <!-- 验证码,可匿名访问 -->
                /validateCode = anon  
                /login = anon
                /doLogin = anon
                <!--请求logout,shrio擦除sssion-->
                /logout=logout
                <!-- /** = authc 所有url都必须认证通过才可以访问 -->
                /**=authc
            </value>
        </property>
<!--         <property name="filterChainDefinitionMap" ref="filterChainDefinitionMap" /> -->
    </bean>
     <!-- 配置一个 bean, 该 bean 实际上是一个 Map. 通过实例工厂方法的方式 -->
<!--     <bean id="filterChainDefinitionMap"  -->
<!--      factory-bean="filterChainDefinitionMapBuilder" factory-method="buildFilterChainDefinitionMap"></bean> -->
<!--     <bean id="filterChainDefinitionMapBuilder" -->
<!--      class="com.web.maven.factory.FilterChainDefinitionMapBuilder"></bean> -->

shiro-ehcache.xml中配置缓存如下:

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

数据表sessions创建语句如下:

create table sessions (
  id varchar(200),
  session varchar(2000),
  constraint pk_sessions primary key(id)
) charset=utf8 ENGINE=InnoDB;

【4】会话验证


Shiro提供了会话验证调度器,用于定期的验证会话是否已过期,如果过期将停止会话。


出于性能考虑,一般情况下都是获取会话时来验证会话是否过期并停止会话的。但是如在web 环境中,如果用户不主动退出是不知道会话是否过期的,因此需要定期的检测会话是否过期。


Shiro提供了会话验证调度器SessionValidationScheduler,也提供了使用Quartz会话验证调度器–QuartzSessionValidationScheduler

目录
相关文章
|
存储 缓存 NoSQL
Shiro 解决分布式 Session
在分布式系统中,会话管理是一个重要的问题。Shiro框架提供了一种解决方案,通过其会话管理组件来处理分布式会话。本文演示通过RedisSessionManager解决分布式会话问题。
103 0
|
存储 NoSQL Java
Spring Session分布式会话管理
Spring Session分布式会话管理
97 0
|
6月前
|
存储 缓存 搜索推荐
session 详解:掌握客户端会话管理
session 详解:掌握客户端会话管理
|
6月前
|
安全 搜索推荐 Java
【SpringSecurity6.x】会话管理
【SpringSecurity6.x】会话管理
85 0
|
安全
【Shiro】2、Shiro实现Session会话过期时间控制
一般我们的 session 会话过期时间默认为 30 分钟,有的用户认为 30 分钟太短了,有时候临时有事出去了,回来已经过期了,工作还没完成就只能登出了,非常不方便,于是要求我们改变 session 的过期时间
999 0
|
安全 Java 数据安全/隐私保护
SpringSecurity-10-Session会话管理
SpringSecurity-10-Session会话管理
135 0
|
存储 缓存 开发框架
shiro会话管理
Shiro提供了完整的企业级会话管理功能,不依赖于底层容器(如Tomcat、WebLogic),不管是J2SE还是J2EE环境都可以使用,提供了会话管理,会话事件监听,会话存储/持久化,容器无关的集群,失效/过期支持,对Web的透明支持,SSO单点登录的支持等特性。
|
应用服务中间件 容器
Shiro配置cookie以及共享Session和Session失效问题
Shiro配置cookie以及共享Session和Session失效问题
437 1
|
Web App开发 安全 Java
Spring Security系列教程20--会话管理之会话并发控制
前言 现在我们已经掌握了如何防御会话固定攻击,以及在会话过期时的处理策略,但是这些都是针对单个HttpSession来说的,对于会话来说,我们还有另一种情况需要考虑:会话并发控制! 那什么是会话并发控制呢?假如我想实现 “在我们的网站中,同一时刻只允许一个用户登录” 这样的效果,该怎么做? 请各位带着以上的这些问题,跟着 一一哥 继续往下学习吧。 一. 会话并发控制 1. 会话并发控制 首先我们来了解一下会话并发控制的概念。 有时候出于安全的目的,我们可能会有这样的需求,就是规定在同一个系统中,只允许一个用户在一个终端上登录,这其实就是对会话的并发控制。 2. 并发控制实现思路 如果我们
521 0
|
缓存 NoSQL 前端开发
Shiro - Spring + Jedis(会话、缓存、自动登录)整合篇(下)
Shiro - Spring + Jedis(会话、缓存、自动登录)整合篇(下)
196 0
Shiro - Spring + Jedis(会话、缓存、自动登录)整合篇(下)