spring+shiro多节点session共享

2023-11-10

shiro我就不多介绍了,我的方案是重写 shiro的sessionDAO,把session存储到redis上,直接上代码

一、spring中配置

 <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
       <!--  <property name="cacheManager" ref="shiroEhcacheManager"/> -->
        <property name="sessionManager" ref="sessionManager"></property>  
        <property name="realm" ref="shiroDbRealm"/>
       <!--  <property name="sessionMode" value="http"/> -->
 </bean>
首先是securityManager,由于是web项目,使用的是shiro自带的DefaultWebSecurityManager,这里没有必要注入cacheManager,通过查看源码可知,在这里配的cacheManager最终会被set到sessionDAO中,由于我们要自己写sessionDAO,所以没必要,很多人没有看源码,以为在这儿配了就能用,其实不然,要使得cacheManager生效,你接下来配置的sessionManager和sessionDAO都必须实现CacheManagerAware接口才行,sessionMode这个属性,已经被shiro废弃,后续shiro版本可能就没了,这里的sessionMode属性影响是很大的,配置不同会影响sessionManager是否被重置,我们要配自己的sessionDAO,所有绝对是不用配该属性的

接下来是sessionManager了

<bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager"> 
    	<!-- 设置全局会话超时时间,默认30分钟(1800000) -->  
        <property name="globalSessionTimeout" value="7200000" />  
        <!-- session存储的实现 -->  
        <property name="sessionDAO" ref="redisSessionDAO" />
        <property name="sessionIdCookie.name" value="JSID"/>
</bean>
sessionManager使用shiro的DefaultWebSessionManager,设置了session的超时时间,配置自己写的session,重点要说的时这个sessionIdCookie.name,shiro默认是通过cookie保存的sessionId,cookie的key默认为JSESSIONID,例如:JSESSIONID=5575866c-96e5-4c34-9b3c-ebe2c07fd423,而tomcat或者其他的一些用到的框架可能会重写这个键值对,因为JSESSIONID这个名字实在是太通用了,一旦被复写,shiro自然就找不到sessionid对应的session了,这会导致一连串的问题,比如用着用着跳到登录页面,获取session中存好的参数获取不到等,为了避免这个问题,配置了cookie的name

sessionDAO配置及redisManager配置

<bean id="redisSessionDAO" class="com.xxx.shiro.RedisSessionDAO">
     <property name="redisManager" ref="redisManager" />
</bean>
 <bean id="redisManager" class="com.xxx.shiro.RedisManager">
     <property name="host" value="${redis.hostname}"/>
     <property name="port" value="${redis.port}"/>
     <property name="expire" value="7200"/>
	    <!-- optional properties:
	    <property name="timeout" value="10000"/>
	    <property name="password" value="123456"/>
	    -->
</bean>
redisManager主要用来访问redis的,当然也可以用spring提供的redis的template,我反正是没用成功,有兴趣的朋友试下

其他shiro配置

<bean id="shiroDbRealm" class="com.xxx.shiro.ShiroDBRealm"></bean>

<bean id=“tokenFilter” class=“com.xxx.shiro.TokenFilter”></bean>

    <!-- Shiro Filter -->
<bean id=“shiroFilter” class=“org.apache.shiro.spring.web.ShiroFilterFactoryBean”>
<property name=“securityManager” ref=“securityManager”/>
<property name=“loginUrl” value="/login"/>
<property name=“successUrl” value="/home"/>
<property name=“unauthorizedUrl” value="/home"/>
<property name=“filters”>
<map>
<entry key=“tkf” value-ref=“tokenFilter”/>
</map>
</property>
<property name=“filterChainDefinitions”>
<value>
/login = anon
/captcha.png = anon
/toLogin = anon
/logout = logout
/static/** = anon
/weixin/** = anon
/cxf/** = anon
/api/oauth2/=anon
/api/
= anon ,tkf
/** = authc
</value>
</property>
</bean>
<!-- 用户授权信息Cache, 采用EhCache -->
<!-- <bean id=“shiroEhcacheManager” class=“org.apache.shiro.cache.ehcache.EhCacheManager”>
<property name=“cacheManagerConfigFile” value=“classpath:ehcache/ehcache-shiro.xml”/>
</bean> -->
<!-- 保证实现了Shiro内部lifecycle函数的bean执行 -->
<bean id=“lifecycleBeanPostProcessor” class=“org.apache.shiro.spring.LifecycleBeanPostProcessor”/>

二、java代码

public class RedisSessionDAO extends AbstractSessionDAO {
	private static Logger logger = LoggerFactory.getLogger(RedisSessionDAO.class);
	private RedisManager redisManager;
	/**
	 * The Redis key prefix for the sessions 
	 */
	private String keyPrefix = "shiro_redis_session:";
	@Override
	public void update(Session session) throws UnknownSessionException {
		this.saveSession(session);
	}
	/**
	 * save session
	 * @param session
	 * @throws UnknownSessionException
	 */
	private void saveSession(Session session) throws UnknownSessionException{
		if(session == null || session.getId() == null){
			logger.error("session or session id is null");
			return;
		}
		byte[] key = getByteKey(session.getId());
		byte[] value = SerializeUtils.serialize(session);
		session.setTimeout(redisManager.getExpire()*1000);		
		this.redisManager.set(key, value, redisManager.getExpire());
	}
	@Override
	public void delete(Session session) {
		if(session == null || session.getId() == null){
			logger.error("session or session id is null");
			return;
		}
		redisManager.del(this.getByteKey(session.getId()));
	}
	@Override
	public Collection<Session> getActiveSessions() {
		Set<Session> sessions = new HashSet<Session>();
		Set<byte[]> keys = redisManager.keys(this.keyPrefix + "*");
		if(keys != null && keys.size()>0){
			for(byte[] key:keys){
				Session s = (Session)SerializeUtils.deserialize(redisManager.get(key));
				sessions.add(s);
			}
		}
		return sessions;
	}
	@Override
	protected Serializable doCreate(Session session) {
		Serializable sessionId = this.generateSessionId(session);  
        this.assignSessionId(session, sessionId);
        this.saveSession(session);
		return sessionId;
	}
	@Override
	protected Session doReadSession(Serializable sessionId) {
		if(sessionId == null){
			logger.error("session id is null");
			return null;
		}
		Session s = (Session)SerializeUtils.deserialize(redisManager.get(this.getByteKey(sessionId)));
		return s;
	}
	/**
	 * 获得byte[]型的key
	 * @param key
	 * @return
	 */
	private byte[] getByteKey(Serializable sessionId){
		String preKey = this.keyPrefix + sessionId;
		return preKey.getBytes();
	}
	public RedisManager getRedisManager() {
		return redisManager;
	}
	public void setRedisManager(RedisManager redisManager) {
		this.redisManager = redisManager;
		/**
		 * 初始化redisManager
		 */
		this.redisManager.init();
	}
	/**
	 * Returns the Redis session keys
	 * prefix.
	 * @return The prefix
	 */
	public String getKeyPrefix() {
		return keyPrefix;
	}
	/**
	 * Sets the Redis sessions key 
	 * prefix.
	 * @param keyPrefix The prefix
	 */
	public void setKeyPrefix(String keyPrefix) {
		this.keyPrefix = keyPrefix;
	}
}
在保存session时,每次都重新设置了session的过期时间,按理说应该不需要,为了保险吧

public class RedisManager {
	private String host = "127.0.0.1";
	private int port = 6379;
	// 0 - never expire
	private int expire = 0;
	//timeout for jedis try to connect to redis server, not expire time! In milliseconds
	private int timeout = 0;
	private String password = "";
	private static JedisPool jedisPool = null;
	public RedisManager(){
	}
	/**
	 * 初始化方法
	 */
	public void init(){
		if(jedisPool == null){
			if(password != null && !"".equals(password)){
				jedisPool = new JedisPool(new JedisPoolConfig(), host, port, timeout, password);
			}else if(timeout != 0){
				jedisPool = new JedisPool(new JedisPoolConfig(), host, port,timeout);
			}else{
				jedisPool = new JedisPool(new JedisPoolConfig(), host, port);
			}
		}
	}
	/**
	 * get value from redis
	 * @param key
	 * @return
	 */
	public byte[] get(byte[] key){
		byte[] value = null;
		Jedis jedis = jedisPool.getResource();
		try{
			value = jedis.get(key);
		}finally{
			//jedisPool.returnResource(jedis);该方法会导致高并发访问时,报[B cannot be cast to java.lang.Long]异常             jedis.close();
		}
		return value;
	}
	/**
	 * set 
	 * @param key
	 * @param value
	 * @return
	 */
	public byte[] set(byte[] key,byte[] value){
		Jedis jedis = jedisPool.getResource();
		try{
			jedis.set(key,value);
			if(this.expire != 0){
				jedis.expire(key, this.expire);
		 	}
		}finally{
			jedis.close();
		}
		return value;
	}
	/**
	 * set 
	 * @param key
	 * @param value
	 * @param expire
	 * @return
	 */
	public byte[] set(byte[] key,byte[] value,int expire){
		Jedis jedis = jedisPool.getResource();
		try{
			jedis.set(key,value);
			if(expire != 0){
				jedis.expire(key, expire);
		 	}
		}finally{
			jedis.close();
		}
		return value;
	}
	/**
	 * del
	 * @param key
	 */
	public void del(byte[] key){
		Jedis jedis = jedisPool.getResource();
		try{
			jedis.del(key);
		}finally{
			jedis.close();
		}
	}
	/**
	 * flush
	 */
	public void flushDB(){
		Jedis jedis = jedisPool.getResource();
		try{
			jedis.flushDB();
		}finally{
			jedis.close();
		}
	}
	/**
	 * size
	 */
	public Long dbSize(){
		Long dbSize = 0L;
		Jedis jedis = jedisPool.getResource();
		try{
			dbSize = jedis.dbSize();
		}finally{
			jedis.close();
		}
		return dbSize;
	}
	/**
	 * keys
	 * @param regex
	 * @return
	 */
	public Set<byte[]> keys(String pattern){
		Set<byte[]> keys = null;
		Jedis jedis = jedisPool.getResource();
		try{
			keys = jedis.keys(pattern.getBytes());
		}finally{
			jedis.close();
		}
		return keys;
	}
	public String getHost() {
		return host;
	}
	public void setHost(String host) {
		this.host = host;
	}
	public int getPort() {
		return port;
	}
	public void setPort(int port) {
		this.port = port;
	}
	public int getExpire() {
		return expire;
	}
	public void setExpire(int expire) {
		this.expire = expire;
	}
	public int getTimeout() {
		return timeout;
	}
	public void setTimeout(int timeout) {
		this.timeout = timeout;
	}
	public String getPassword() {
		return password;
	}
	public void setPassword(String password) {
		this.password = password;
	}
}
序列化用的util类

public class SerializeUtils {
	private static Logger logger = LoggerFactory.getLogger(SerializeUtils.class);
	/**
	 * 反序列化
	 * @param bytes
	 * @return
	 */
	public static Object deserialize(byte[] bytes) {
		Object result = null;
		if (isEmpty(bytes)) {
			return null;
		}
		try {
			ByteArrayInputStream byteStream = new ByteArrayInputStream(bytes);
			try {
				ObjectInputStream objectInputStream = new ObjectInputStream(byteStream);
				try {
					result = objectInputStream.readObject();
				}
				catch (ClassNotFoundException ex) {
					throw new Exception("Failed to deserialize object type", ex);
				}
			}
			catch (Throwable ex) {
				throw new Exception("Failed to deserialize", ex);
			}
		} catch (Exception e) {
			logger.error("Failed to deserialize",e);
		}
		return result;
	}
	public static boolean isEmpty(byte[] data) {
		return (data == null || data.length == 0);
	}
	/**
	 * 序列化
	 * @param object
	 * @return
	 */
	public static byte[] serialize(Object object) {
		byte[] result = null;
		if (object == null) {
			return new byte[0];
		}
		try {
			ByteArrayOutputStream byteStream = new ByteArrayOutputStream(128);
			try  {
				if (!(object instanceof Serializable)) {
					throw new IllegalArgumentException(SerializeUtils.class.getSimpleName() + " requires a Serializable payload " +
							"but received an object of type [" + object.getClass().getName() + "]");
				}
				ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteStream);
				objectOutputStream.writeObject(object);
				objectOutputStream.flush();
				result =  byteStream.toByteArray();
			}
			catch (Throwable ex) {
				throw new Exception("Failed to serialize", ex);
			}
		} catch (Exception ex) {
			logger.error("Failed to serialize",ex);
		}
		return result;
	}
}



本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

spring+shiro多节点session共享 的相关文章

随机推荐

  • jQuery实现MD5加密

    原文地址 http blog csdn net you23hai45 article details 52629234 1 问题背景 有两个输入框 一个输入明文 另一个输入框显示密文 2 实现源码 html view plain copy
  • Linux平台的IO操作

    什么是IO如何理解IO IO就是输入与输出 I Input 从键盘拷贝数据至内存中 O Output 从内存中拷贝数据到终端上 因为Linux平台基本思想为 一切皆文件 所以 对于IO可以再次理解位 I 从文件中拷贝数据到内存中去 O 从内
  • WebService 学习笔记之一 HelloWorld

    一 开发环境 我的开发环境是 MyEclipse 10 Apache cxf 2 3 0 相关jar包下载地址 http www apache org dist cxf 2 3 0 二 开发步骤 创建Server 1 新建一个Java工程C
  • Linux/Android 充电框架/流程分析 1

    1 内核注册供电设备 内核通过 devm power supply register 或者 power supply register 注册供电相关的设备 2 获取电量的方式 电量是电池相关的信息 所以 注册的供电设备为 battery 例
  • [深入研究4G/5G/6G专题-46]: 5G Link Adaption链路自适应-2-常见缩略语

    A CSI Aperiodic Channel Status Information 非周期性信道状态信息 AL Aggregation level for PDCCH 聚合等级 ATB Adaptive Transmission Band
  • 如何从一个git服务器仓库将项目迁移到另一个git服务器仓库

    最近服务器迁移涉及到代码也需要一块迁移 梳理了一些git服务迁移指令 希望大家共享 从服务器A迁移到服务器B 1 首先将服务器A上的代码进行备份 1 1 git备份指令 从A服务器 https gitlab xxxx cn 上clone代码
  • Kali环境下渗透目标(win7)虚拟机

    Kali环境下渗透目标 win7 虚拟机 注 此文章 是我看了CSDN上另一位博主的文章 我在自己在电脑上实验了一遍的结果 另外 本文仅供学习交流 用来做非法之事 本人概不负责 准备 1 在电脑上安装VMware 并下载安装虚拟机kali
  • pycharm运行YOLOv5 (一)

    登录github com search yolov5 查看选择各个版本 查看发布各个版本 点击下载版本 下载zip文件 解压之后导入pycharm 先看这个文件requirements txt 这是启动yolov5需要的各个库 pip in
  • docker启动rabbitmq后无法访问15672端口

    docker启动rabbitmq后无法访问15672端口 经查找资料得知 rabbitmq默认web界面管理插件是关闭的 只要通过命令开启就行 1 docker ps查看rabbitmq的id fb7a78201d31 2 命令进入容器do
  • vivado下载

    vitis vivado 2019 2百度网盘 链接 https pan baidu com s 11CvUL05o2NTRqN4PpnFG5Q 提取码 n82v vivado2018 2百度网盘 链接 https pan baidu co
  • javaEE 2019 10 24关于运算符 键盘录入数据

    运算符 对变量和常量的操作过程称为运算 对变量和常量进行操作的符号 运算符 算术运算符 加 减 乘 除 自增 自减 取模 字符串连接 赋值运算符
  • WEB前端 期末复习 2018.11

    WEB前端 期末复习 2018 11 名词解释部分 API Application Programming Interface 应用程序编程接口 是一些预先定义的函数 目的是提供应用程序与开发人员基于某软件或硬件得以访问一组例程的能力 而又
  • kafka不同的topic使用相同的group的问题

    前几天为了省事 在申请group的时候 就使用了原来的group 本来以为group从属于某一个topic topic不同 group之间相互不会影响 但实际情况不是这样的 kafka不同topic的consumer如果用的groupid名
  • android 下拉刷新 组件,Android实现简单的下拉刷新控件

    背景 列表控件在Android App开发中用到的场景很多 在以前我们用ListView GradView 现在应该大多数开发者都已经在选择使用RecyclerView了 谷歌给我们提供了这些方便的列表控件 我们可以很容易的使用它们 但是在
  • maven跳过单元测试-maven.test.skip和skipTests的区别以及部分常用命令

    DskipTests 不执行测试用例 但编译测试用例类生成相应的class文件至target test classes下 Dmaven test skip true 不执行测试用例 也不编译测试用例类 不执行测试用例 但编译测试用例类生成相
  • poll, select, epoll

    随着2 6内核对epoll的完全支持 网络上很多的文章和示例代码都提供了这样一个信息 使用epoll代替传统的poll能给网络服务应用带来性能上的提升 但大多文章里关于性能提升的原因解释的较少 这里我将试分析一下内核 2 6 21 1 代码
  • HDU - 1312 Red and Black(DFS)

    There is a rectangular room covered with square tiles Each tile is colored either red or black A man is standing on a bl
  • 微信小程序导出当前画布指定区域的内容并生成图片保存到本地相册(canvas)

    最近在学小程序 在把当前画布指定区域的内容导出并生成图片保存到本地这个知识点上踩坑了 这里用到的方法是 wx canvasToTempFilePath 该方法作用是把当前画布指定区域的内容导出生成指定大小的图片 并返回文件路径 详情 看文档
  • Idea快捷键大全(Windows)

    转载 Idea快捷键大全 Windows Lymanyu的博客 CSDN博客 idea快捷键
  • spring+shiro多节点session共享

    shiro我就不多介绍了 我的方案是重写 shiro的sessionDAO 把session存储到redis上 直接上代码 一 spring中配置