springboot配置shiro多项目实现session共享的详细步骤

x33g5p2x  于2021-11-28 转载在 Spring  
字(6.8k)|赞(0)|评价(0)|浏览(536)

springboot配置shiro多项目实现session共享的详细步骤

公司需要这样的需求:

有两个项目master 主项目、suiteone 项目,两个项目各自由shiro 安全框架管理,当不能登录时,都无法访问,但当登录了其中一个,再访问另一个的时候不再需要登录即可访问。
如果想看为什么需要共享session ,可以去看我这篇文章。[shiro框架—关于多项目之间验证为什么需要共享session]

关于实现多项目共享session的逻辑介绍

其实在上边的链接里我已经说明了,但是怕大家不去看,所以我就又复制到了这里:
先来说一下我的理解 ,如下:

上图中master 项目为主项目,登录页即在这个项目中,suiteonesuitetwo 为两个从项目,当两个从项目有请求时,如果没有登录的时候,都会打到master 项目的登录页上。共享session 采用的是redis 存储。

上图的步骤如下:
  1. 浏览器请求master 项目,第一次请求的时候,也是会带着浏览器中的cookie 去请求,当然第一次去redis 里肯定找不到对应的session,会通过⑤进入到登录页。
  2. 当在登录页输入完正确的账号密码后,才能登录成功,否则仍会回到⑤。
  3. 在这一步的时候,会将登录成功后的session ,根据它,将生成sessionId串 ,并传到前端浏览器中,浏览器以cookie 存储。
  4. 同时将第③步中生成的session 存储到redis 中。
  5. 当前这里,不只是当登录失败的时候,会进入到登录页中,当浏览器长时间没有访问后台(每次浏览器访问后台,其实都会刷新session 的过期时间expireTime),导致session 超过时,也会进入到该步中。
  6. 当浏览器请求suiteonesuteTwo 这两个从项目时,肯定也是将当前浏览器中的所有的cookie 设置到request headers 请求头中。
  7. 根据传入的sessionId串 到共享的redis 存储中匹配。
  8. 如果匹配不到,则会跳转到master 项目的登录页,如果匹配成功,则会访问通过。

以上描述的并不难,大家也都会想到,那么如何将扯了这么多的淡 真正更简单的,落地实现才是大家关注的。

多项目通过redis共享session的步骤
1、shiro 共享session 的切入点,下图是shiro的验证流程

2、配置过程

说一下实现shiro 项目共享session 的配置步骤:

(1)项目多添加redis 依赖。
  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-redis</artifactId>
  4. <version>1.3.6.RELEASE</version>
  5. </dependency>
(2)配置application.properties 文件
  1. ###主项目写成自己项目的登录页面路径,从项目必须写完整的主项目登录页面url
  2. shiro.loginUrl=/login
  3. ###主从项目的下边名字必须一致
  4. shiro.jessionid=sessionId
  5. ###redis连接配置
  6. spring.redis.host=192.168.1.160
  7. spring.redis.port=6379
  8. spring.redis.password=znxd
(3)重写session 增删改查,与redis接入
  1. package microservice.sc.shiro;
  2. import org.apache.shiro.session.Session;
  3. import org.apache.shiro.session.UnknownSessionException;
  4. import org.apache.shiro.session.mgt.eis.AbstractSessionDAO;
  5. import org.springframework.beans.factory.annotation.Autowired;
  6. import org.springframework.data.redis.core.RedisTemplate;
  7. import org.springframework.stereotype.Service;
  8. import java.io.Serializable;
  9. import java.util.Collection;
  10. import java.util.concurrent.TimeUnit;
  11. @Service
  12. public class RedisSessionDao extends AbstractSessionDAO {
  13. // Session超时时间,单位为毫秒
  14. private long expireTime = 120000;
  15. @Autowired
  16. private RedisTemplate redisTemplate;// Redis操作类,对这个使用不熟悉的,可以参考前面的博客
  17. public RedisSessionDao() {
  18. super();
  19. }
  20. public RedisSessionDao(long expireTime, RedisTemplate redisTemplate) {
  21. super();
  22. this.expireTime = expireTime;
  23. this.redisTemplate = redisTemplate;
  24. }
  25. @Override // 更新session
  26. public void update(Session session) throws UnknownSessionException {
  27. System.out.println("===============update================");
  28. if (session == null || session.getId() == null) {
  29. return;
  30. }
  31. session.setTimeout(expireTime);
  32. redisTemplate.opsForValue().set(session.getId(), session, expireTime, TimeUnit.MILLISECONDS);
  33. }
  34. @Override // 删除session
  35. public void delete(Session session) {
  36. System.out.println("===============delete================");
  37. if (null == session) {
  38. return;
  39. }
  40. redisTemplate.opsForValue().getOperations().delete(session.getId());
  41. }
  42. @Override
  43. // 获取活跃的session,可以用来统计在线人数,如果要实现这个功能,可以在将session加入redis时指定一个session前缀,统计的时候则使用keys("session-prefix*")的方式来模糊查找redis中所有的session集合
  44. public Collection<Session> getActiveSessions() {
  45. System.out.println("==============getActiveSessions=================");
  46. return redisTemplate.keys("*");
  47. }
  48. @Override// 加入session
  49. protected Serializable doCreate(Session session) {
  50. System.out.println("===============doCreate================");
  51. Serializable sessionId = this.generateSessionId(session);
  52. this.assignSessionId(session, sessionId);
  53. redisTemplate.opsForValue().set(session.getId(), session, expireTime, TimeUnit.MILLISECONDS);
  54. return sessionId;
  55. }
  56. @Override// 读取session
  57. protected Session doReadSession(Serializable sessionId) {
  58. System.out.println("==============doReadSession=================");
  59. if (sessionId == null) {
  60. return null;
  61. }
  62. return (Session) redisTemplate.opsForValue().get(sessionId);
  63. }
  64. public long getExpireTime() {
  65. return expireTime;
  66. }
  67. public void setExpireTime(long expireTime) {
  68. this.expireTime = expireTime;
  69. }
  70. public RedisTemplate getRedisTemplate() {
  71. return redisTemplate;
  72. }
  73. public void setRedisTemplate(RedisTemplate redisTemplate) {
  74. this.redisTemplate = redisTemplate;
  75. }
  76. }
(4)将重写的RedisSessionDao 接入到shiro 中的sessionManager
  1. @Bean
  2. public RedisSessionDao getRedisSessionDao(){
  3. return new RedisSessionDao();
  4. }
  5. /** * @see DefaultWebSessionManager * @return */
  6. @Bean(name="sessionManager")
  7. public DefaultWebSessionManager defaultWebSessionManager() {
  8. DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
  9. //sessionManager.setCacheManager(cacheManager());
  10. sessionManager.setGlobalSessionTimeout(43200000); //12小时
  11. sessionManager.setDeleteInvalidSessions(true);
  12. //关键在这里
  13. sessionManager.setSessionDAO(getRedisSessionDao());
  14. sessionManager.setSessionValidationSchedulerEnabled(true);
  15. sessionManager.setDeleteInvalidSessions(true);
  16. sessionManager.setSessionIdCookie(getSessionIdCookie());
  17. return sessionManager;
  18. }
(5)将sessionManager 注入到securityManager
  1. /** * @see org.apache.shiro.mgt.SecurityManager * @return */
  2. @Bean(name="securityManager")
  3. public DefaultWebSecurityManager securityManager() {
  4. DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
  5. manager.setRealm(userRealm());
  6. //manager.setCacheManager(cacheManager());
  7. manager.setSessionManager(defaultWebSessionManager());
  8. return manager;
  9. }
(6)设置登录页的地址cookie名字

首先引入我们刚才application.properties文件中的内容:

  1. @Value("${shiro.loginUrl}")
  2. private String masterLoginUrl;
  3. @Value("${shiro.jessionid}")
  4. private String jessionId;

然后注入到配置中:

  1. @Bean(name = "shiroFilter")
  2. public ShiroFilterFactoryBean shiroFilter(){
  3. ShiroFilterFactoryBean bean = new MShiroFilterFactoryBean(); //指向自定义过滤器,自定义过滤器对js/css等忽略
  4. bean.setSecurityManager(securityManager());
  5. //在这里设置登录页
  6. bean.setLoginUrl(masterLoginUrl);
  7. Map<String, Filter>filters = new LinkedHashMap<>();
  8. filters.put("anon", new AnonymousFilter());
  9. bean.setFilters(filters);
  10. //shiro配置过滤规则少量的话可以用hashMap,数量多了要用LinkedHashMap,保证有序,原因未知
  11. Map<String, String> chains = new LinkedHashMap<>();
  12. chains.put("/login","anon");
  13. chains.put("/loginForm","anon");
  14. chains.put("/**", "authc");
  15. bean.setFilterChainDefinitionMap(chains);
  16. return bean;
  17. }
  1. /** * 给shiro的sessionId默认的JSSESSIONID名字改掉 * @return */
  2. @Bean(name="sessionIdCookie")
  3. public SimpleCookie getSessionIdCookie(){
  4. SimpleCookie simpleCookie = new SimpleCookie(jessionId);
  5. return simpleCookie;
  6. }

注意,如果值注入不进来,则看一下当前的shiro 配置文件里的LifecycleBeanPostProcessor 的注入是否为static 方法,如下:

  1. /** * 该类如果不设置为static,@Value注解就无效,原因未知 * @return */
  2. @Bean
  3. public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
  4. return new LifecycleBeanPostProcessor();
  5. }
(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的接口,即可以访问成功

配置完成,如有问题请及时留言。

相关文章