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不需要重新登录。

原文地址:https://www.cnblogs.com/LUA123/p/7728319.html