zoukankan      html  css  js  c++  java
  • Redis+Shiro+Spring-data-redis,共享Session

    环境:centos7,Java1.8+,一个Nginx,两个Tomcat,一个Redis。

    关于共享session的问题大家都应该知道了,传统的部署项目,两个相同的项目部署到不同的服务器上,Nginx负载均衡后会导致用户在A上登陆了,经过负载均衡后,在B上要重新登录,因为A上有相关session信息,而B没有。这种情况也称为“有状态”服务。而“无状态”服务则是:在一个公共的地方存储session,每次访问都会统一到这个地方来拿。

    为了方便我只用了一台linux虚拟机。

    如果你使用了Shiro权限管理呢,他的好处之一就是会话管理,由Shiro管理Session,而不是Tomcat。听说都在用Spring-Session,实际上它和shiro一样,请看web.xml里面的配置

    <!-- spring-session Filter 
        <filter>
            <filter-name>springSessionRepositoryFilter</filter-name>
            <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        </filter>
        <filter-mapping>
            <filter-name>springSessionRepositoryFilter</filter-name>
            <url-pattern>/*</url-pattern>
        </filter-mapping>
        -->
    
        <!-- Shiro Filter is defined in the spring application context: -->
        <filter>
            <filter-name>shiroFilter</filter-name>
            <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
            <init-param>
                <param-name>targetFilterLifecycle</param-name>
                <param-value>true</param-value>
            </init-param>
        </filter>
    
        <filter-mapping>
            <filter-name>shiroFilter</filter-name>
            <url-pattern>/*</url-pattern>
            <dispatcher>REQUEST</dispatcher>
            <dispatcher>FORWARD</dispatcher>
            <dispatcher>INCLUDE</dispatcher>
            <dispatcher>ERROR</dispatcher>
        </filter-mapping>

    不难看出,这两个都是基于:org.springframework.web.filter.DelegatingFilterProxy

    下面开始正题。

    使用Shiro需要继承AbstractSessionDAO

    package com.internetsaying.auth.session;
    
    import java.io.Serializable;
    import java.util.Collection;
    import java.util.List;
    
    import org.apache.shiro.session.Session;
    import org.apache.shiro.session.UnknownSessionException;
    import org.apache.shiro.session.mgt.eis.AbstractSessionDAO;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    import com.internetsaying.auth.redis.RedisManager;
    
    @Component
    public class MySessionDao extends AbstractSessionDAO {
    
        @Autowired
        private RedisManager redisManager;
        
        @Override
        public void delete(Session session) {
            if(session == null || session.getId() == null){
                System.out.println("Session is null");
                return;
            }
            redisManager.hdelete(session.getId().toString());
        }
    
        @Override
        public Collection<Session> getActiveSessions() {
            List<Session> list = redisManager.hmget();
            return list;
        }
    
        @Override
        public void update(Session session) throws UnknownSessionException {
            if(session == null || session.getId() == null){
                System.out.println("Session is null");
                return;
            }
            Serializable sessionId = session.getId();
            redisManager.hadd(sessionId.toString(), session);
        }
    
        @Override
        protected Serializable doCreate(Session session) {
            Serializable sessionId = generateSessionId(session);
            assignSessionId(session, sessionId);
            //添加进redis
            redisManager.hadd(sessionId.toString(), session);
            
            return sessionId;
        }
    
        @Override
        protected Session doReadSession(Serializable sessionId) {
            return redisManager.hget(sessionId.toString());
        }
    
    
        
    }

    下面是RedisManager的代码

    package com.internetsaying.auth.redis;
    
    import java.util.ArrayList;
    import java.util.List;
    
    import org.apache.shiro.session.Session;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.stereotype.Component;
    
    @Component
    public class RedisManager {
    
        @Autowired
        private RedisTemplate<String, Session> redisTemplate;
        
        private static final String KEY = "shareSessionMap";
        
        public void hadd(String sessionId, byte[] bytes){
            redisTemplate.boundHashOps(KEY).put(sessionId, bytes);
        }
        public void hadd(String sessionId, Session session){
            redisTemplate.boundHashOps(KEY).put(sessionId, session);
        }
        
        public void hdelete(String sessionId){
            redisTemplate.boundHashOps(KEY).delete(sessionId);
        }
        
        public Session hget(String sessionId){
            return (Session) redisTemplate.boundHashOps(KEY).get(sessionId);
        }
        
        public List<Session> hmget(){
            List<Session> list = new ArrayList<>();
            
            List<Object> values = redisTemplate.boundHashOps(KEY).values();
            for (Object object : values) {
                list.add((Session) object);
            }
            return list;
        }
    }

    配置文件:shiro和redis部分(只是与共享session相关的代码)

    <?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"
        xmlns:core="http://activemq.apache.org/schema/core"
        xmlns:redis="http://www.springframework.org/schema/redis"
        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-4.3.xsd
            http://activemq.apache.org/schema/core http://activemq.apache.org/schema/core
            http://www.springframework.org/schema/redis http://www.springframework.org/schema/redis/spring-redis-1.0.xsd">
    
    
        <!-- SecurityManager -->
        <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
            <!-- session管理 -->
            <property name="sessionManager" ref="sessionManager"></property>
        </bean>
        <!-- redis操作 -->
        <bean id="redisManager" class="com.internetsaying.auth.redis.RedisManager"></bean>
        <!-- Session ID 生成器 -->
        <bean id="sessionIdGenerator" class="org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator"></bean>
        <!-- SessionDao实现类 -->
        <bean id="sessionDAO" class="com.internetsaying.auth.session.MySessionDao">
            <property name="sessionIdGenerator" ref="sessionIdGenerator"></property>
        </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="sessionDAO"></property>
            <!-- sessionIdCookie的实现,用于重写覆盖容器默认的JSESSIONID -->
            <property name="sessionIdCookie" ref="sharesession" />
        </bean>
        <!-- sessionIdCookie的实现,用于重写覆盖容器默认的JSESSIONID -->
        <bean id="sharesession" class="org.apache.shiro.web.servlet.SimpleCookie">
            <!-- cookie的name,对应的默认是 JSESSIONID -->
            <constructor-arg name="name" value="SHAREJSESSIONID" />
            <!-- jsessionId的path为 / 用于多个系统共享jsessionId -->
            <property name="path" value="/" />
            <property name="httpOnly" value="true"/>
        </bean>
        
        <!-- 以下是Redis -->
        <context:property-placeholder location="classpath:redis.properties"/>
        <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
            <property name="maxIdle" value="${redis.maxIdle}"></property>
            <property name="maxTotal" value="${redis.maxTotal}"></property>
            <property name="maxWaitMillis" value="${redis.maxWaitMillis}"></property>
            <property name="testOnBorrow" value="${redis.testOnBorrow}"></property>
        </bean>
    
        <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
            <property name="usePool" value="${redis.pooled}"></property>
            <property name="hostName" value="${redis.host}"></property>
            <property name="port" value="${redis.port}"></property>
            <property name="poolConfig" ref="jedisPoolConfig"></property>
        </bean>
        <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
            <property name="connectionFactory" ref="jedisConnectionFactory"></property>
            <property name="keySerializer">
                <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"></bean>
            </property>
            <!-- 解决读取int类型value值报错的问题 -->
            <property name="valueSerializer">
                <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"></bean>
            </property>
            <property name="hashKeySerializer">
                <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"></bean>
            </property>
        </bean>
        
    </beans>

    配置文件:redis.properties

    #Redis settings
    
    redis.host=127.0.0.1
    redis.port=6379
    #redis.pass=
    redis.maxIdle=30
    redis.maxTotal=100
    redis.maxWaitMillis=1000
    redis.testOnBorrow=true
    redis.pooled=true

    执行流程

    说明:由于做的是分布式项目,不便贴上全部代码,如遇问题可联系我:3110320051.

    我有个完整的小栗子

    部署

    关于Nginx做负载均衡可参考我上一篇文章。

    这里将项目打包后,分别上传到两个Tomcat,启动。当访问时。【页面过于简单,仅供测试】

    点击登录既完成Shiro的认证授权,跳转到可访问页面。

    刷新

    注意cookie的Value

    现在我们看一下redis存储的:

    [root@localhost local]# ./bin/redis-cli 
    127.0.0.1:6379> keys *
    1) "shareSessionMap"
    127.0.0.1:6379> HKEYS shareSessionMap
    1) "a511c5c4-9f61-4c24-aad2-b551f8053ebf"

    所以:每次服务器都是去redis里面去拿的session,登录A后,切换到B不需要重新登录。

  • 相关阅读:
    java mail使用qq邮箱发邮件的配置方法
    (利用tempdata判断action是直接被访问还是重定向访问)防止微信活动中用户绕过关注公众号的环节
    判断浏览器为微信浏览器
    解决表单(搜索框)回车的时候直接提交了表单不运行js的问题
    传智播客JavaWeb day11--事务的概念、事务的ACID、数据库锁机制、
    传智播客JavaWeb day10-jdbc操作mysql、连接数据库六大步骤
    页面上常用的一些小功能--QQ、回到顶部
    手机端禁止网页页面放大代码
    Resharp注册码
    NueGet设置package Source
  • 原文地址:https://www.cnblogs.com/LUA123/p/7728319.html
Copyright © 2011-2022 走看看