这周工作中遇到的一个问题就是要把组内的两个项目(完全不同的两个项目,没有任何关联那种)做成统一入口。这里用A,B来代指两个项目。
现在要做成去掉B的登录,然后在A的项目里用iframe的形式把B的页面嵌入到A项目中。首先这两个项目的权限是单独在另一个平台获取的。所以理论上这种实现是完全可行的。但是具体的实现其实一步一个坑。当然了有一些也是我个人问题。下面从头开始讲思路。
第一步思路:Session从内存中移出
首先简单介绍下我这里为什么会说到Session共享。因为之前两个项目都是基于session实现的鉴权。所以出于最小改动原则,我这里还是要继续使用session而非JWT。
然后说到两个项目的鉴权都是用session实现的,但是基于内存的session肯定是无法实现共享。所以这里第一步是换成redis-session。
redis-session的使用
两个项目都是spring项目,所以大大简化了引入redis-session的过程。主要分散步:
1. 导入依赖
<!-- 引入 spring-session 依赖 -->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<!-- 引入 redis 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
需要注意的是如果我们只是使用redis存储session,是不需要引入下面的redis依赖的。我这里之所以引入这个依赖是为了对redis进行一些查询操作。
2. 添加redis配置
关于redis的配置,最基本的连接配置,还有一个很重要的注解,就是session的存储方式是redis。如下:
spring:
session:
store-type: redis #session 存储类型
redis:
database: 2
host: 127.0.0.1
port: 6379
因为我这里是测试环境,所以也没设置密码。如果你的redis还想有一些别的配置按需自己配吧。
3. 启动类添加注解
@EnableRedisHttpSession //开启使用redis存储session
只需要这三步,我们就会发现session存入redis中了。我们可以用测试代码试一下,请求中随便set到session中点东西。
@GetMapping("/test")
public Object test(HttpServletRequest request){
request.getSession().setAttribute("test",12);
return null;
}
然后我们打开redis可视化软件看一下是不是增加了一个session:
到了这里,session存到redis中已经实现,两个项目都改好之后,该考虑如何共享了。
第二步思路:共享redis中的session
试错一:假装是一个session
到了这里网上开始有参差不齐的声音了。一种是说什么判断是不是一个session是根据cookie中传的session值,然后我就信了。当时第一反应,让两个项目的cookie中的session值是一样的不就行了么
然后我本来是打算获取A项目中请求的session,然后传给B项目的一个接口。让前端把这个值设置成session的值。
一顿操作后发现不可行。最后想到一个很好的测试方法:A登录完成后获取到session,我直接在postman中调用B服务的接口,将session的值写入。请求后发现request.getSession(false)是空。所以根据说明这个判断方式完全行不通。
反正我也不知道是我操作问题还是这个说法本来就是假的,如果有实测成功了的朋友欢迎来辩
试错二:nginx转发
这是我们组的一个大佬提出来的思路。就是前端请求都用A服务的域名包装一下,这样session肯定是一致的。
首先在B项目的前端配置:请求路径改成a.com/bbb/xxx
这里的a.com是A项目的请求地址,/bbb是自定义的一个基础前缀。为了区分实际上的b项目的请求。 /xxx是原本b项目接口的请求路径。
比如原本b的一个接口: b.com/user/info
现在改成: a.com/bbb/user/info
然后在A项目的nginx中配置一个转发:
因为我们项目的私密性,所以这里贴出来的是百度的转发方法。其实就是设置/bbb 的请求转发到b项目原本的后端地址上。
理论上请求是同一个地址出来的,session应该是一个了吧。然后运行项目一跑,发现转过去的还是没有session。
后来百度是因为域名不同,自动清了cookie。因为nginx这块的配置一直都是我一个同事来的。所以到此这个想法就失败了。
当然我之前百度说有一种方式可以跨域名,事实证明这种配置没啥用。不确定是不是因为我们这边前端有什么特殊处理(因为浏览器查看的session和存到redis中的sessionId不一致)
location ~ /xxx/ {
proxy_cookie_domain b.com a.com;
proxy_pass http://b.com;
}
勉强实现三: 以A模块为核心代码维护B模块的session
这里其实不算是试错了,但是实现的方式我自己挺不满意的。当然了时间紧任务重,目前这个功能是这么实现的。
因为需求的前提是A,B两个项目会存在session过期时间不一致。而且B是挂在A项目里的,这就有个问题,B项目session失效的话,其实是没办法跳到登录页的。所以要保持session过期时间一致。所以如果不能共享的话,手动维护两个session有效性一致也可以。
这也就是我上面引入依赖的时候要引入data-redis的原因啦。
这个代码维护就简单的很。获取A项目的sessionId。然后放在B项目请求的header中.然后在b项目中做判断:
- A有session,B也有session,放行
- A没有session,B也没有session,放行
- A没有session,B有sesion, B的session删除
- A有session,B没有session,A的session删除
以上四个逻辑可以保证A,B的session要么同时生效,要么同时不生效。代码逻辑其实也很简单,我就截图的形式展现下。
另外使用redis的时候还有有个小插曲,最开始我用的是RedisTemplate。当时也就图个简单,结果发现获取不到key。最后发现这种带有:的层级结构的key,不能直接取出。哪怕用redisTemplata.key("*")也获取不到。当然了也存不了。
估计是有什么用法, 但是因为我以前项目中我知道层级结构的是可以取的,所以找了半天原因最后换成StringRedisTemplate就可以了。
还有一个就是默认的redis-session存session的时候序列化有点问题,取出来的不是存进去的字符串,这是因为默认会采用jdk的序列化方式。这个单独设置下序列化方式就行了。
到这里其实让A,B伪共享session就算实现了。之所以说伪共享是因为只做到了一个没有另一个自动删除。更合理的应该是一个存在另一个自动创建。这个其实也容易实现。就是发现其中一个存在自动调用另一个的登录接口创建session,我这里图简单也没写。我上面说觉得这种方式不太满意是每次都校验其实挺没必要的。但是目前也没找到更合适的方式。
本篇笔记就记到这里,如果稍微帮到你了记得点个喜欢点个关注。如果大家有更好的实现方式欢迎提出,或者有什么建议也行,共同进步嘛!也祝大家工作顺顺利利!