redis cluster+spring+shiro实现session集群共享

Spring Date是Spring的一个子项目,它的诞生用于简化数据库访问,即支持NoSql数据也支持关系型数据库,让数据库数据的操作变得更加简单,下面主要实现的是Spring Date操作redis数据库.
为什么要共享session? 传统的部署项目,两个相同的项目部署到不同的服务器上,Nginx负载均衡后会导致用户在A上登陆了,经过负载均衡后,在B上要重新登录,因为A上有相关session信息,而B没有。这种情况也称为“有状态”服务。而“无状态”服务则是:在一个公共的地方存储session,每次访问都会统一到这个地方来拿。这样我们用redis进行session的储存,集群中所有的服务器共享redis储存的session信息,这样就避免了用户信息不统一的尴尬情况.
为什么要用redis作为储存的数据库呢?redis是非关系型数据库,也就是nosql数据库,此类数据库最大特点是,不会受到sql数据的限制,存取速度快,且吞吐量高,非常适合在高并发的集群中作为储存Session首选数据库
shiro的亮点之一就是会话管理,所以我们用其管理session数据

1.添加所需的Jar包

在jar包的选择上切记要版本兼容,笔者在实验中,半天的时间浪费在错误的查找中,最后知晓是jar包版本不兼容问题,下面我给出大家jar版本参考

序号 jar
1 spring-data-commons-1.8.4
2 jedis-2.9.0
3 spring-data-redis-1.8.3
4 commons-pool2-2.4.2

2.配置spring-redis.xml

此配置文件主要是把redis-cluster集群的连接与设置交由spring容器完成

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context
                          http://www.springframework.org/schema/context/spring-context.xsd">

    <!--引入配置文件-->
    <bean id="configBean" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="location">
            <value>
                classpath:redis.properties
            </value>
        </property>
    </bean>
    <!--配置redisClusterconfigure也就是关于rediscluster的连接信息存放与配置-->
    <bean id="redisClusterConfiguration" class="org.springframework.data.redis.connection.RedisClusterConfiguration">
        <property name="maxRedirects" value="${redis.cluster.max.redirect}"/>
        <property name="clusterNodes">
            <!--clsternode要注入一个redisNode的set集合-->
            <list>
            <bean class="org.springframework.data.redis.connection.RedisClusterNode">
                <!--constructor-arg是注入构造方法中的值-->
                <constructor-arg index="0" value="${redis.cluster.node1.host}"/>
                <constructor-arg index="1" value="${redis.cluster.node1.port}"/>
            </bean>
            <bean class="org.springframework.data.redis.connection.RedisClusterNode">
                <constructor-arg index="0" value="${redis.cluster.node2.host}"/>
                <constructor-arg index="1" value="${redis.cluster.node2.port}"/>
            </bean>
                <bean class="org.springframework.data.redis.connection.RedisClusterNode">
                    <constructor-arg index="0" value="${redis.cluster.node3.host}"/>
                    <constructor-arg index="1" value="${redis.cluster.node3.port}"/>
                </bean>
                <bean class="org.springframework.data.redis.connection.RedisClusterNode">
                    <constructor-arg index="0" value="${redis.cluster.node4.host}"/>
                    <constructor-arg index="1" value="${redis.cluster.node4.port}"/>
                </bean>
                <bean class="org.springframework.data.redis.connection.RedisClusterNode">
                    <constructor-arg index="0" value="${redis.cluster.node5.host}"/>
                    <constructor-arg index="1" value="${redis.cluster.node5.port}"/>
                </bean>
                <bean class="org.springframework.data.redis.connection.RedisClusterNode">
                    <constructor-arg index="0" value="${redis.cluster.node6.host}"/>
                    <constructor-arg index="1" value="${redis.cluster.node6.port}"/>
                </bean>
                <bean class="org.springframework.data.redis.connection.RedisClusterNode">
                    <constructor-arg index="0" value="${redis.cluster.node7.host}"/>
                    <constructor-arg index="1" value="${redis.cluster.node7.port}"/>
                </bean>
                <bean class="org.springframework.data.redis.connection.RedisClusterNode">
                    <constructor-arg index="0" value="${redis.cluster.node8.host}"/>
                    <constructor-arg index="1" value="${redis.cluster.node8.port}"/>
                </bean>
            </list>
        </property>
    </bean>
    <!--SpringData主要功能是用于序列化和反序列化,将对象变为可读的二进制数据,存取与redis,方便在实际开发中
        进行数据的交互,序列化和反序列化是springdata进行自动处理,配置自带的redis操作模板即可,在redis中
        只有hash和基本类型可以处理,所以只要配置这两个类型的key和value操作即可-->
    <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
       <!--取得连接工厂(操作对象)-->
        <property name="connectionFactory" ref="jedisConnection"/>
        <!--定义序列化处理的类,用的是stringRedisSerializer,表明key用String的序列化处理-->
        <property name="keySerializer">
            <bean id="stringRedisSerializer" class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
        </property>
        <!--定义value的处理的类,用的是对象处理序列化-->
        <property name="valueSerializer">
            <bean id="jdkSerializationRedisSerializer" class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer"/>
        </property>
        <!--配置hash类型value操作-->
        <property name="hashValueSerializer" >
            <bean id="hashjdkSerializationRedisSerializer" class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer"/>
        </property>
        <!--配置hash类型的key-->
        <property name="hashKeySerializer">
            <bean id="hashstringRedisSerializer" class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
        </property>
    </bean>
    <!--Jedis连接池相关属性的注入-->
    <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
        <property name="testOnBorrow" value="true"/>
        <property name="maxWaitMillis" value="${redis.pool.maxWaitMills}"/>
        <property name="maxTotal" value="${redis.pool.maxTotal}"/>
        <property name="maxIdle" value="${redis.pool.maxIdle}"/>
    </bean>
    <!--Jedis连接池的配置,交由SpringDate进行配置-->
    <!--jedis连接工厂类由Springdata提供,用于处理连接池配置,主要将属性注入构造方法-->
    <bean id="jedisConnection" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
        <property name="poolConfig" ref="jedisPoolConfig"/>
        <!--注入构造方法中名字为clusterconfig的节点参数-->
        <constructor-arg name="clusterConfig" ref="redisClusterConfiguration"/>
    </bean>
</beans>

3.重写shiro会话管理类EnterpriseCacheSessionDAO

public class RedisSessionDao extends EnterpriseCacheSessionDAO {
    @Resource
    private RedisTemplate<String,Object> redisTemplate;
    //创建session并返回session数据

    @Override
    protected Serializable doCreate(Session session){
        System.out.println("doCreate"+session+"*******");
        Serializable sessionID= super.doCreate(session);
        //将创建好的sessionID序列传入redis数据库
        redisTemplate.opsForValue().set(sessionID.toString(),session,1800);
        return sessionID;
    }

    //根据sessionID读取session数据,此方法主要目的在于先去本地址读取session,如果没有
    //那么还有一种可能是此数据在别的服务器上,那么我们不能跨服务器读session,只能从redis
    //数据中查看是否有此sessionID,如果还没有表示时间到期,无session需要重新登陆
    @Override
    protected Session doReadSession(Serializable sessionId) {
        System.out.println("doreadsession"+sessionId);
        Session session =super.doReadSession(sessionId);
        if (session==null){
            return (Session) redisTemplate.opsForValue().get(sessionId.toString());
        }
        return null;
    }

    //session有个存储时间,更新session的更新
    @Override
    protected void doUpdate(Session session) {
        System.out.println("doUpdate"+session+"*******");
        if (session!=null){ //如果更新的时候session还存在,那么还需要再去存数据库一次
            redisTemplate.opsForValue().set(session.getId().toString(),session,1800);
        }
        super.doUpdate(session);
    }

    //执行session的删除处理
    @Override
    protected void doDelete(Session session) {
        System.out.println("doDelete"+session+"**********");
        super.doDelete(session);
        redisTemplate.delete(session.getId().toString());
    }

}

4.配置spring-shiro.xml

此配置文件主要完成配置sessionID生成器,并将其交给Dao类进行处理

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context
                          http://www.springframework.org/schema/context/spring-context.xsd">
<!--Session ID 生成器 -->
     <bean id="sessionIdGenerator" class="org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator"></bean>

    <!--自定义session处理类-->
      <bean id="RedisSessionDao" class="cn.travel.Session.RedisSessionDao">
          <!--将ID生成器放入自定义Session类,因为继承了父类,父类需要一个sessionID生成器-->
          <property name="sessionIdGenerator" ref="sessionIdGenerator"/>
      </bean>
    <!--session管理 -->
    <bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
        <property name="globalSessionTimeout" value="1800000"></property>
        <property name="deleteInvalidSessions" value="true"></property>
        <property name="sessionDAO" ref="RedisSessionDao"></property>
        <!-- sessionIdCookie的实现,用于重写覆盖容器默认的JSESSIONID -->
        <!--<property name="sessionIdCookie" ref="sharesession" />-->
    </bean>


    <!--配置安全管理器-->
    <bean id="scurityManage" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <!--配置你需要使用的Realms-->
        <property name="realm" ref="empRealm"/>
        <property name="sessionManager" ref="sessionManager"/>
    </bean>


    <!--配置shiro过滤器-->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <!--配置一个安全过滤器-->
        <property name="securityManager" ref="scurityManage"/>
        <!--认证成功后跳转的路径-->
        <property name="successUrl" value="/ListAll.action"/>
        <!--出现错误的时候跳转路径-->
        <property name="loginUrl" value="/login.jsp"/>

        <!--shiro里面需要针对所有的路径进行配置,所有的配置都要经过文本的形式设置
        本质上也就是shiro.ini中的[urls]-->
        <property name="filterChainDefinitions">
            <!--在此shiro过滤路径中,authc代表必须登陆才可以,rememberMe无效-->
            <!--user,代表只要登陆过,记住密码也可以-->
            <value>
                /emp-add.jsp=user
                /emp-list.jsp=user
                /travel-creat.jsp=user
                /ListAll.action=user
                /LimitEmp.action=user
                 /TravelsList.action=user
            </value>
        </property>
    </bean>

    <!--配置shiro的生命周期由Spring进行控制-->
    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>

    <!--shiro框架配置annotation操作,项目为了方便操作,普遍annotation只在控制层控制方法进行使用
    很少在项目网页路径上检测-->
    <!--1.启动Annotation在shiro中相关操作-->
    <bean id="defaultAdvisorAutoProxyCreator"
          class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
          depends-on="lifecycleBeanPostProcessor">
        <!--进行目标代理类处理控制-->
        <property name="proxyTargetClass" value="true"/>
    </bean>
    <!--2.针对安全管理Aop实现也就是启动spring代理shiro的scurityManager-->
    <bean id="authorizationAttributeSourceAdvisor"
          class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
        <property name="securityManager" ref="scurityManage"/>
    </bean>
</beans>

4.启动tomcat和redis-cluster集群

启动后进行测试,当进入登陆页面后,发现此时shiro会分配一个sessionID
在这里插入图片描述
在这里插入图片描述

登陆后去redis-cluster集群中查看是否有session相关数据被存入
在这里插入图片描述
查看此session的二进制数据
在这里插入图片描述

成功!!!

发布了30 篇原创文章 · 获赞 4 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/qq_42549122/article/details/90415078