springboot配置shiro多项目实现session共享的详细步骤
2023-09-14 09:06:18 时间
springboot配置shiro多项目实现session共享的详细步骤
公司需要这样的需求:
有两个项目master
主项目、suiteone
项目,两个项目各自由shiro
安全框架管理,当不能登录时,都无法访问,但当登录了其中一个,再访问另一个的时候不再需要登录即可访问。
如果想看为什么需要共享session
,可以去看我这篇文章。[shiro框架—关于多项目之间验证为什么需要共享session]
关于实现多项目共享session的逻辑介绍
其实在上边的链接里我已经说明了,但是怕大家不去看,所以我就又复制到了这里:
先来说一下我的理解
,如下:
上图中master
项目为主项目,登录页即在这个项目中,suiteone
、suitetwo
为两个从项目,当两个从项目有请求时,如果没有登录的时候,都会打到master
项目的登录页上。共享session
采用的是redis
存储。
上图的步骤如下:
- 浏览器请求
master
项目,第一次请求的时候,也是会带着浏览器中的cookie
去请求,当然第一次去redis
里肯定找不到对应的session
,会通过⑤进入到登录页。 - 当在登录页输入完正确的账号密码后,才能登录成功,否则仍会回到⑤。
- 在这一步的时候,会将登录成功后的
session
,根据它,将生成sessionId串
,并传到前端浏览器中,浏览器以cookie
存储。 - 同时将第③步中生成的
session
存储到redis
中。 - 当前这里,不只是当登录失败的时候,会进入到登录页中,当浏览器长时间没有访问后台(每次浏览器访问后台,其实都会刷新
session
的过期时间expireTime
),导致session
超过时,也会进入到该步中。 - 当浏览器请求
suiteone
、suteTwo
这两个从项目时,肯定也是将当前浏览器中的所有的cookie
设置到request headers
请求头中。 - 根据传入的
sessionId串
到共享的redis
存储中匹配。 - 如果匹配不到,则会跳转到
master
项目的登录页,如果匹配成功,则会访问通过。
以上描述的并不难,大家也都会想到,那么如何将扯了这么多的淡
真正更简单的,落地实现才是大家关注的。
多项目通过redis共享session的步骤
1、shiro
共享session
的切入点,下图是shiro的验证流程
2、配置过程
说一下实现shiro
项目共享session
的配置步骤:
(1)项目多添加redis
依赖。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-redis</artifactId>
<version>1.3.6.RELEASE</version>
</dependency>
(2)配置application.properties
文件
###主项目写成自己项目的登录页面路径,从项目必须写完整的主项目登录页面url
shiro.loginUrl=/login
###主从项目的下边名字必须一致
shiro.jessionid=sessionId
###redis连接配置
spring.redis.host=192.168.1.160
spring.redis.port=6379
spring.redis.password=znxd
(3)重写session
增删改查,与redis接入
package microservice.sc.shiro;
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 org.springframework.stereotype.Service;
import java.io.Serializable;
import java.util.Collection;
import java.util.concurrent.TimeUnit;
@Service
public class RedisSessionDao extends AbstractSessionDAO {
// Session超时时间,单位为毫秒
private long expireTime = 120000;
@Autowired
private RedisTemplate redisTemplate;// Redis操作类,对这个使用不熟悉的,可以参考前面的博客
public RedisSessionDao() {
super();
}
public RedisSessionDao(long expireTime, RedisTemplate redisTemplate) {
super();
this.expireTime = expireTime;
this.redisTemplate = redisTemplate;
}
@Override // 更新session
public void update(Session session) throws UnknownSessionException {
System.out.println("===============update================");
if (session == null || session.getId() == null) {
return;
}
session.setTimeout(expireTime);
redisTemplate.opsForValue().set(session.getId(), session, expireTime, TimeUnit.MILLISECONDS);
}
@Override // 删除session
public void delete(Session session) {
System.out.println("===============delete================");
if (null == session) {
return;
}
redisTemplate.opsForValue().getOperations().delete(session.getId());
}
@Override
// 获取活跃的session,可以用来统计在线人数,如果要实现这个功能,可以在将session加入redis时指定一个session前缀,统计的时候则使用keys("session-prefix*")的方式来模糊查找redis中所有的session集合
public Collection<Session> getActiveSessions() {
System.out.println("==============getActiveSessions=================");
return redisTemplate.keys("*");
}
@Override// 加入session
protected Serializable doCreate(Session session) {
System.out.println("===============doCreate================");
Serializable sessionId = this.generateSessionId(session);
this.assignSessionId(session, sessionId);
redisTemplate.opsForValue().set(session.getId(), session, expireTime, TimeUnit.MILLISECONDS);
return sessionId;
}
@Override// 读取session
protected Session doReadSession(Serializable sessionId) {
System.out.println("==============doReadSession=================");
if (sessionId == null) {
return null;
}
return (Session) redisTemplate.opsForValue().get(sessionId);
}
public long getExpireTime() {
return expireTime;
}
public void setExpireTime(long expireTime) {
this.expireTime = expireTime;
}
public RedisTemplate getRedisTemplate() {
return redisTemplate;
}
public void setRedisTemplate(RedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
}
(4)将重写的RedisSessionDao
接入到shiro
中的sessionManager
@Bean
public RedisSessionDao getRedisSessionDao(){
return new RedisSessionDao();
}
/**
* @see DefaultWebSessionManager
* @return
*/
@Bean(name="sessionManager")
public DefaultWebSessionManager defaultWebSessionManager() {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
//sessionManager.setCacheManager(cacheManager());
sessionManager.setGlobalSessionTimeout(43200000); //12小时
sessionManager.setDeleteInvalidSessions(true);
//关键在这里
sessionManager.setSessionDAO(getRedisSessionDao());
sessionManager.setSessionValidationSchedulerEnabled(true);
sessionManager.setDeleteInvalidSessions(true);
sessionManager.setSessionIdCookie(getSessionIdCookie());
return sessionManager;
}
(5)将sessionManager
注入到securityManager
/**
* @see org.apache.shiro.mgt.SecurityManager
* @return
*/
@Bean(name="securityManager")
public DefaultWebSecurityManager securityManager() {
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
manager.setRealm(userRealm());
//manager.setCacheManager(cacheManager());
manager.setSessionManager(defaultWebSessionManager());
return manager;
}
(6)设置登录页的地址
和cookie名字
首先引入我们刚才application.properties
文件中的内容:
@Value("${shiro.loginUrl}")
private String masterLoginUrl;
@Value("${shiro.jessionid}")
private String jessionId;
然后注入到配置中:
@Bean(name = "shiroFilter")
public ShiroFilterFactoryBean shiroFilter(){
ShiroFilterFactoryBean bean = new MShiroFilterFactoryBean(); //指向自定义过滤器,自定义过滤器对js/css等忽略
bean.setSecurityManager(securityManager());
//在这里设置登录页
bean.setLoginUrl(masterLoginUrl);
Map<String, Filter>filters = new LinkedHashMap<>();
filters.put("anon", new AnonymousFilter());
bean.setFilters(filters);
//shiro配置过滤规则少量的话可以用hashMap,数量多了要用LinkedHashMap,保证有序,原因未知
Map<String, String> chains = new LinkedHashMap<>();
chains.put("/login","anon");
chains.put("/loginForm","anon");
chains.put("/**", "authc");
bean.setFilterChainDefinitionMap(chains);
return bean;
}
/**
* 给shiro的sessionId默认的JSSESSIONID名字改掉
* @return
*/
@Bean(name="sessionIdCookie")
public SimpleCookie getSessionIdCookie(){
SimpleCookie simpleCookie = new SimpleCookie(jessionId);
return simpleCookie;
}
注意,如果值注入不进来,则看一下当前的shiro
配置文件里的LifecycleBeanPostProcessor
的注入是否为static
方法,如下:
/**
* 该类如果不设置为static,@Value注解就无效,原因未知
* @return
*/
@Bean
public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
(7)测试
我的项目如下:
如下图所示,我们是springboot+dubbo+zookeeper
的项目:
分别启动30000端口
的主项目master
项目、300001端口的
从项目suiteone
项目,启动后,
- 首先访问主项目
http://localhost:30000
肯定进入登录页。 - 当再访问
http://localhost:30001/test
,因为没有登录直接被重定向到http://localhost:30000
的登录页上,即主项目的登录页。 - 当在当前登录页上登录成功后,再次访问
http://localhost:30001/test
,该接口即返回了数值。 - 当我在主项目的
http://localhost:30000
中退出登录,再次访问http://localhost:30001/test
,同样重定向了登录页。 - 应该能确定实现了
session
共享。
具体如下图:
登录页面,在这时访问30001
也会进入下边这个页面:
登录成功后:
登录成功后访问30001
的接口,即可以访问成功
配置完成,如有问题请及时留言。
相关文章
- springboot引入第三方jar方式,使用scope:system配置systemPath编译,不用添加到本地仓库!
- linux后台运行springboot项目
- springboot中配置mybatis别名该怎么写?
- SpringBoot标签之@ConfigurationProperties、@PropertySource注解的使用
- SpringBoot配置属性之NOSQL
- SpringBoot配置属性之Server
- 【SpringBoot笔记03】SpringBoot框架之读取配置文件属性的六种方式
- SpringBoot 配置使用 mybatis-plus 逆向工程
- SpringBoot - resource资源文件的打包配置详解(指定资源文件位置)
- 【springboot系列】自定义一个拦截器,附源码
- Springboot依赖汇总
- 【Docker】Maven打包SpringBoot项目成Docker镜像并上传到Harbor仓库(Eclipse、STS、IDEA、Maven通用)
- SpringBoot配置Shiro时@RequiresRoles不起作用
- springboot整合多数据源解决分布式事务
- Springboot内置的工具类之FileCopyUtils
- SpringBoot最简单服务配置
- 【springboot】18、内置 Tomcat 配置和切换