一、背景
当前很多公司的项目都采用分布式或者微服务架构来设计,这个时候在用户登录系统上就会产生问题,用户登录时的用户信息如何保证共享,如果不共享,那么在分布式架构下用户会反复登录,这样显然是不合理的。此时问题点就来了,如何保证用户登录信息共享呢?
二、shiro用户信息共享的核心思想
shiro框架做用户的登录和授权时,主要是采用session来进行处理,当用户登录时,shiro会将用户信息相关信息存储到session里并设置唯一的sessionId,sessionId作为cookie传给前端,前端登录后下次再访问时,shiro会去解析cookie的值去取session值,然后进行判断用户是否登录合法等操作。此时读者就可以看到,将session进行共享即可。共享的方式很多,可以采用数据库共享,mogodb共享,redis共享。总之采用一种可以存储数据且可以独立部署的工具作为中间转换工具都可,此处是采用redis进行共享的。
三、代码实现
shiro的session的在SessionDao中进行CRUD等一系列操作,此处需要重写其中的方法,将session的存储和获取修改为从redis中取。
package com.dx.shiro.shiro_deom.Test;
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.data.redis.core.RedisTemplate;
import java.io.Serializable;
import java.util.*;
import java.util.concurrent.TimeUnit;
public class RedisSessionDao extends AbstractSessionDAO {
public long PC_EXPIRE_TIME = 60*60;//秒为单位
@Autowired
RedisTemplate redisTemplate;
@Override
protected Serializable doCreate(Session session) {
Serializable sessionId = SessionCons.TOKEN_PREFIX + UUID.randomUUID().toString();
assignSessionId(session, sessionId);
redisTemplate.opsForValue().set(sessionId, session);
redisTemplate.expire(sessionId, PC_EXPIRE_TIME, TimeUnit.SECONDS);
System.out.println("创建session==>:"+sessionId);
return sessionId;
}
@Override
protected Session doReadSession(Serializable sessionId) {
Session session = (Session) redisTemplate.opsForValue().get(sessionId);
if (null != session) {
session.setTimeout(PC_EXPIRE_TIME * 1000);
}
System.out.println("读取redis的session"+session.getId());
return session;
}
@Override
public void update(Session session) throws UnknownSessionException {
if (null != session && null != session.getId()) {
redisTemplate.opsForValue().set(session.getId(), session);
session.setTimeout(PC_EXPIRE_TIME * 1000);
redisTemplate.expire(session.getId(), PC_EXPIRE_TIME,
TimeUnit.SECONDS);
}
}
@Override
public void delete(Session session) {
redisTemplate.opsForValue().getOperations().delete(session.getId());
}
@Override
public Collection<Session> getActiveSessions() {
Set<Serializable> keys = redisTemplate
.keys(SessionCons.TOKEN_PREFIX_KEY);
if (keys.size() == 0) {
return Collections.emptySet();
}
List<Session> sessions = redisTemplate.opsForValue().multiGet(keys);
return Collections.unmodifiableCollection(sessions);
}
}
RedisSessionDao里面的方法通过方法名即可判断是做什么操作的,这里就不再多讲解。
四、配置sessionManger
需要将重写的sessionDao给配置上,这样才能使用
@Bean
public DefaultWebSessionManager getSessionManager(){
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setSessionDAO(getRedisSession());
sessionManager.setDeleteInvalidSessions(true);// 删除过期的session
sessionManager.setSessionValidationSchedulerEnabled(true);// 是否定时检查session
return sessionManager;
}
@Bean
public RedisSessionDao getRedisSession(){
return new RedisSessionDao();
}
配置完成seesionManger之后,需要将sessionManger交给SecurityManager
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 设置realm.
securityManager.setRealm(myRealm());//此处为自定义的AuthorizingRealm
securityManager.setRememberMeManager(rememberMeManager());//此处为CookieRememberMeManager
securityManager.setSessionManager(getSessionManager());
return securityManager;
}
常量类:
public interface SessionCons {
String LOGIN_USER_PERMISSIONS = "session_login_user_permissions";
String LOGIN_USER_SESSION = "session_login_user";
String TOKEN_PREFIX = "web_session_key-";
String TOKEN_PREFIX_KEY = "web_session_key-*";
String DEVICE_TYPE = "device_type";
}
下面是springboot对redis的一些基本配置,可作参考。
# Redis数据库索引(默认为0)
spring.redis.database=0
# Redis服务器地址
spring.redis.host=localhost
# Redis服务器连接端口
spring.redis.port=6379
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.jedis.pool.max-idle=8
# 连接池中的最小空闲连接
spring.redis.jedis.pool.min-idle=0
# 连接超时时间(毫秒)
spring.redis.timeout=3000
接下来交给ShiroFilterFactoryBean的一些操作就不多讲述了,前两篇关于shiro的都有相关说明。