首页 > 数据库 >第十三节 Shiro集成Redis实现分布式集群Session共享

第十三节 Shiro集成Redis实现分布式集群Session共享

时间:2022-10-07 19:00:17浏览次数:78  
标签:Session Redis sessionId session debug import LOGGER Shiro


一、使用Redis共享Session原理

所有服务器的session信息都存储到了同一个Redis集群中,即所有的服务都将 Session 的信息存储到 Redis 集群中,无论是对 Session 的注销、更新都会同步到集群中,达到了 Session 共享的目的。

        Cookie 保存在客户端浏览器中,而 Session 保存在服务器上。客户端浏览器访问服务器的时候,服务器把客户端信息以某种形式记录在服务器上,这就是 Session。客户端浏览器再次访问时只需要从该 Session 中查找该客户的状态就可以了。

共享数据都会放到外部缓存容器中。
 

第十三节 Shiro集成Redis实现分布式集群Session共享_分布式

二、SessionManager的配置

重要提示:

JDK序列化策略来序列化SimpleSession,因此这就是为什么我们把SimpleSession转成字节存储的原因。所以说,RedisTemplate的value的序列化策略必须要使用JdkSerializationRedisSerializer。

为了能够让多个服务器共享Session,我们需要把Session存储到外部的缓存设备。

       我们需要让session在集群中共享,就需要替换Shiro默认的sessionManager。我们需要使用DefaultWebSessionManager作为SessionManager。

<!-- securityManager 对象-->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
...
<!-- 引入sessionManager-->
<property name="sessionManager" ref="sessionManager"/>
</bean>

<!-- 会话管理器 ,时间单位是毫秒-->
<bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
<!--去掉URL中的JSESSIONID-->
<property name="sessionIdUrlRewritingEnabled" value="false"/>
<!-- 会话存活时间(毫秒),最好与Redis中缓存时间一致 -->
<property name="globalSessionTimeout" value="600000"/><!-- 10分钟 -->
<!-- 是否删除无效的session-->
<property name="deleteInvalidSessions" value="true"/>
<!-- 扫描session线程,负责清理超时会话 -->
<property name="sessionValidationSchedulerEnabled" value="true"/>
<!-- 使用的是QuartZ组件来定时清理-->
<property name="sessionValidationScheduler" ref="sessionValidationScheduler"/>
<!-- session需要使用会话cookie模版-->
<property name="sessionIdCookieEnabled" value="true"/>
<property name="sessionIdCookie" ref="sessionIdCookie"/>
<!-- 对session进行增删错改查的实现类
,如果不自己注入sessionDAO,defaultWebSessionManager会使用MemorySessionDAO用内存做为默认实现类-->
<!-- javascript:void(0) -->
<property name="sessionDAO" ref="sessionDAO"/>
</bean>

<!-- 会话验证调度器 ,时间单位是毫秒-->
<bean id="sessionValidationScheduler" class="org.apache.shiro.session.mgt.
quartz.QuartzSessionValidationScheduler">
<property name="sessionValidationInterval" value="30000"/>
<property name="sessionManager" ref="sessionManager"/>
</bean>

<!-- 会话 ID 生成器 -->
<bean id="sessionIdGenerator" class="org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator"/>

<!-- 自定义session会话存储的实现类 ,使用Redis来存储共享session,达到分布式部署目的-->
<bean id="sessionDAO" class="com.jay.shiro.RedisSessionDao">
<property name="redisService" ref="redisService"/>
<!-- Session的过期时间,单位秒。session将存储在Redis集群中实现共享-->
<!-- Redis设置半小时的缓存失效时间 -->
<property name="expireSeconds" value="600000"/>
</bean>

        SessionManager中的SessionDao是自定义session会话存储的实现类 ,使用Redis来存储共享session,达到分布式部署目的。SessionDao的代码如下。

package com.jay.shiro;

import com.jay.redis.RedisService;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;
import org.apache.shiro.session.mgt.eis.AbstractSessionDAO;
import org.apache.shiro.util.CollectionUtils;
import org.slf4j.Logger;

import java.io.Serializable;
import java.nio.charset.Charset;
import java.util.Collection;
import java.util.Set;

import static com.google.common.collect.Sets.newHashSet;
import static com.jay.util.DateTransformTools.DEFAULT_FORMAT;
import static com.jay.util.DateTransformTools.dateToDateStr;
import static org.slf4j.LoggerFactory.getLogger;

/**
* @author jay.zhou
* @date 2019/1/15
* @time 13:29
*/
public final class RedisSessionDao extends AbstractSessionDAO {

private static final Logger LOGGER = getLogger(RedisSessionDao.class);
/**
* 此编码需要与 RedisServiceImpl 类中编码一致
* 用于解析每个session的Key
*/
private static final String DEFAULT_CHARSET = "UTF-8";

/**
* Redis接口服务
*/
private RedisService redisService;

/**
* 过期时间
*/
private Long expireSeconds;

/**
* shiro-redis的session对象前缀
*/
private String keyPrefix = "shiro_redis_session:";

public RedisService getRedisService() {
return redisService;
}

public void setRedisService(RedisService redisService) {
this.redisService = redisService;
}

public Long getExpireSeconds() {
return expireSeconds;
}

public void setExpireSeconds(Long expireSeconds) {
this.expireSeconds = expireSeconds;
}

public String getKeyPrefix() {
return keyPrefix;
}

public void setKeyPrefix(String keyPrefix) {
this.keyPrefix = keyPrefix;
}

@Override
public void update(Session session) throws UnknownSessionException {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("更新Session:{}", session.getId());
}
this.saveSession(session);
}

@Override
public void delete(Session session) {
if (session == null || session.getId() == null) {
LOGGER.error("session对象(或者sessionId)为空.");
return;
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("删除Session:{}", session.getId());
}
//通过sessionId删除session
redisService.del(this.getByteKey(session.getId()));

}

/**
* 统计当前活动的session
*
* @return 当前活动的session
*/
@Override
public Collection<Session> getActiveSessions() {
final Set<Session> sessions = newHashSet();
//获取缓存中匹配key值的所有键
final Set<byte[]> keys = redisService.keys(this.keyPrefix + "*");
if (!CollectionUtils.isEmpty(keys)) {
for (byte[] key : keys) {
//添加到set集合中
byte[] bytes = redisService.get(key);
Session session = SerializerUtil.deserialize(bytes);
sessions.add(session);
}
}
//shiro的session为我们提供了大量的API接口
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("==========>>统计活动Session(开始)总计活动Session:{}条.<<==========", sessions.size());
for (Session session : sessions) {
LOGGER.debug("ID:{}", session.getId());
LOGGER.debug("有效期:{}秒", session.getTimeout() / 1000);
LOGGER.debug("创建时间:{}", dateToDateStr(session.getStartTimestamp(), DEFAULT_FORMAT));
LOGGER.debug("上次使用时间:{}", dateToDateStr(session.getStartTimestamp(), DEFAULT_FORMAT));
LOGGER.debug(".......................................................................");
}
LOGGER.debug("==========>>统计活动Session(结束)总计活动Session:{}条.<<==========", sessions.size());
}
return sessions;
}

@Override
protected Serializable doCreate(Session session) {
//分配sessionId
final Serializable sessionId = this.generateSessionId(session);
this.assignSessionId(session, sessionId);
//保存session并存储到Redis集群中
this.saveSession(session);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("创建Session:{}", sessionId);
}
return sessionId;
}

@Override
protected Session doReadSession(Serializable sessionId) {
if (sessionId == null) {
LOGGER.error("sessionId为空.");
return null;
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("读取Session:{}", sessionId);
}
//与saveSession是反操作,通过sessionId获取Key的字节数据
final byte[] key = this.getByteKey(sessionId);
//再通过key的字节数据找到value的字节数据
final byte[] value = redisService.get(key);
//最后再反序列化得到session对象
return SerializerUtil.deserialize(value);
}

/**
* 保存session
* sessionId -> key[]
* session -> value[]
*
* @param session Session对象
* @throws UnknownSessionException 未知Session异常
*/
private void saveSession(Session session) throws UnknownSessionException {
if (session == null || session.getId() == null) {
LOGGER.error("session对象(或者sessionId)为空.");
return;
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("保存Session:{}", session.getId());
}
//sessionId -> key[]
final byte[] key = getByteKey(session.getId());
//session -> value[]
final byte[] value = SerializerUtil.serialize(session);
session.setTimeout(getExpireSeconds());
//save To Redis
this.redisService.setEx(key, value, getExpireSeconds());
}

/**
* 获得byte[]型的key
*
* @param sessionId sessionId
* @return byte[]型的key
*/
private byte[] getByteKey(Serializable sessionId) {
final String preKey = this.keyPrefix + sessionId;
return preKey.getBytes(Charset.forName(DEFAULT_CHARSET));
}
}

三、测试

        我们需要将项目复制为两个,第一个项目的端口是8080,第二个项目的端口改为 9090,依次启动两个项目。测试的时候,确保你的Redis服务器是开着的。

        在8080端口项目中尝试访问受限制页面,会被重定向到登录页面。在登录页面输入jay / 123456,登录成功后。在9090端口项目一样点击第一个超链接尝试访问受限制页面,这次发现可以成功请求到后台JSON数据。然后在9090端口尝试退出登录,再回到8080端口的项目尝试访问受限制页面,发现用户已经退出,请求被重定向到登录页面。

       上述现象说明,使用Redis实现分布式Session共享成功。

第十三节 Shiro集成Redis实现分布式集群Session共享_apache_02

        大宇能够成功搭建一个分布式项目,很大一部分原因就是站在巨人的肩膀上。特此鸣谢下方博客与博主。 

        参考文章: ​​Apache shiro集群实现 (六)分布式集群系统下的高可用session解决方案---Session共享​​ 

                           ​​Shiro 分布式架构下 Session 的共享实现​

                           ​​shiro 学习之会话管理​

                           ​​Shiro在Spring的会话管理(session)​

                           ​​序列化工具​

四、源码下载

       本章节项目源码:​​点击我下载源码​

----------------------------------------------------分割线------------------------------------------------------- 

     


标签:Session,Redis,sessionId,session,debug,import,LOGGER,Shiro
From: https://blog.51cto.com/u_15713464/5734896

相关文章

  • ctfshow新手杯剪刀石头布(session反序列化)
    看到ini_set('session.serialize_handler','php');让我不由自主的想起了session反序列化漏洞的一道题。直接百度会有很多文章这里不多介绍。因此我们的解法就是:1.post一......
  • 解决 Redis 报错:WRONGPASS invalid username-password pair or user is disabled.
    问题:在进行Redis简单配置以及单元测试时,出现无法连接6379的异常,具体为认证失败问题:WRONGPASSinvalidusername-passwordpairoruserisdisabled.之前输入authpa......
  • CentOS 7.9 安装 redis-6.2.0
    一、CentOS7.9安装redis-6.2.01下载地址:https://download.redis.io/releases/redis-6.2.0.tar.gz 2安装gcc来进行编译Redis由C语言编写,所以需要系统中有gcc......
  • 小白都能看懂的Redis讲解--针对单个键操作集锦
    1重命名键renamekeynewname可以对键重命名,下面的例子我们创建了一个key为name,value为luke的键值对。然后将name重命名为user,之后查询name就返回nil,而user是可以查到值......
  • redis 面试题汇总
    什么是RedisRedis是一个开源的使用ANSIC语言编写、遵守BSD协议、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API的非关系型数据库......
  • 【Redis】Redis中字典结构
    Redis的字典使用哈希表作为底层实现,一个哈希表里面有多个哈希表节点,而每个哈希表节点保存了字典中的一个键值对(key-value)###1.字典的实现说白了,基本上就是跟Java中的Hash......
  • spring boot 整合 redis
    springboot整合redispom.xmlmaven依赖<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifa......
  • redis 主从复制
    redis主从配置只需要配置从库,redis默认本身是主库查看当前库的信息>inforeplication#查看当前库的信息#Replicationrole:master#角色connected_slaves:......
  • Redis实现布隆过滤器解析
    布隆过滤器原理介绍【1】概念说明1)布隆过滤器(BloomFilter)是1970年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用......
  • Redis做限流应用
    CurrentLimiting在编写系统时候有时候我们的系统在设计的时候就已经估算到了最大请求负载了,如果大量的请求超过系统所能承受着的值时,那么系统可能随时挂掉,所有聪明程序员......