最近小明遇到一个需求: 需要将几个独立的系统 (子系统) 汇总到一个集中的系统 (父系统) 当中, 当用户在父系统登录过后, 再点击这几个子系统, 就可以免登录跳转到任意一个系统. 当时一听, duang~duang~ 就有很多方案涌进来(吹牛的), 但只有下面这个方案得到了 leader 的肯定, 如今已经在线上跑着了, 接下来给大家复盘一下.
看完这个需求, 大家是不是第一感觉就是: 这不就是 SSO(单点登录)系统嘛?
单点登录 (英语: Single sign-on, 缩写为 SSO), 又译为单一签入, 一种对于许多相互关连, 但是又是各自独立的软件系统, 提供访问控制的属性. 当拥有这项属性时, 当用户登录时, 就可以获取所有系统的访问权限, 不用对每个单一系统都逐一登录. 这项功能通常是以轻型目录访问协议(LDAP) 来实现, 在服务器上会将用户信息存储到 LDAP 数据库中. 相同的, 单一退出 (single sign-off) 就是指, 只需要单一的退出动作, 就可以结束对于多个系统的访问权限.
是的, 没错, 小明接到这个需求以后, 整体思路也是按着 SSO 设想的, 但是细想之后, 发现不能完全照搬, 要考虑项目的实际情况: 比如已知的几个子系统是之前的已经开发好的, 不能大动干戈, 需要平滑接入父系统, 而且根据需求, SSO 的功能也没必要全部实现, 简而言之, 就是一个阉割版的 SSO.
小明只需要实现: 用户在父系统账号密码登录后, 通过点击任意一个子系统的功能按钮 (不需要重复输入账号登录) 能够跳转子系统功能页即可.
设计流程
项目
一个简单朴素的 SpringBoot 项目
时序图
说干就干, 用户输入账号密码, 请求 SSO 用户登录模块进行账号密码校验, 校验通过后建立全局会话, 并且返回前端 token 凭证(我使用的是 sessionId), 跳转其他系统时携带 token, 其他系统拿到 token 后, 再调用 SSO 平台进行 token 校验, 如果校验通过, 则用户可在子系统内建立会话, 用户跳转系统完成. 下面给大家举例 SSO 跳转一个子系统的时序图:
在这里插一嘴哈, 我使用的流程图工具是 ProcessOn, 是一款在线画图工具, 非常适合画各种示意图, 体验极佳, 如果大家想尝试一下, 可以使用我的邀请链接注册使用~
整个流程图如上面所示, 下面主要针对各个功能点进行详细说明.
SSO 系统的登录与会话保持
本次会话管理采用的是 Redis session,spring 完美支持 Redis 存储 session 信息, 此外还支持 MongoDB\JDBC\HAZELCAST 等存储会话方式. 通过 Redis 存储 session, 可以满足集群部署, 分布式系统的 session 共享(当然这些都是后话).
pom.xml 依赖配置如下
- <!--redis 依赖 -->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-data-Redis</artifactId>
- </dependency>
- <!--sessions 依赖 -->
- <dependency>
- <groupId>org.springframework.session</groupId>
- <artifactId>spring-session-data-Redis</artifactId>
- </dependency>
application.YAML 配置 Redis 及 session
- spring:
- Redis:
- host: 127.0.0.1
- password: 123456
- port: 6379
- timeout: 1500
- database: 0
- jedis:
- pool:
- max-active: 1000
- max-wait: -1
- max-idle: 10
- min-idle: 5
- # 设置 session 存储类型为 Redis
- session:
- store-type: Redis
此时, Redis 存储会话配置已经完成, 但总觉得缺少什么, 嗷, 原来除此之外, 我们还需要设置 session 的有效时长, application.YAML 中的配置如下:
- server:
- servlet:
- session:
- # 支持 Duration 表达式, 此时表示 120 分钟
- timeout: PT120M
当然, 我们还需要在 Springboot 主方法通过 @EnableRedisHttpSession 开启 Redis session
注意: 如果上面配置的 session 有效时长不生效, 我们可以在注解属性上配置 session 有效时间(不瞒大家, 我就是在此处配置才生效的)
- @SpringBootApplication
- // 开启 Redis session , 并且设置 session 有效时长
- @EnableRedisHttpSession(maxInactiveIntervalInSeconds = 7200)
- public class XiaoMingApplication {
- public static void main(String[] args) {
- SpringApplication.run(XiaoMingApplication.class, args);
- }
- }
至此, sso 系统的登录及会话管理就完成啦. 接下来看一下与其他系统如何交互.
跳转子系统
流程
如果 SSO 已经登录 -> 用户点击某个子系统按钮 (和负责 A 系统的人员约定好的链接) 发起 get 请求 -> A 系统后端接收到请求 -> 调用 SSO 系统进行 token 校验(下面会讲到) -> 建立会话, 例如
http://xxx/jump?token=123456
这是一个在地址栏输入的 get 请求, 该接口需要特殊处理, 后端拦截器需要放行.
参数说明
参数 | 说明 |
---|---|
xxx | 此处 xxx 为域名,jump 为系统 A 提供的接口 url 地址,可以自定义,需要约定告知 |
token | 返回的校验凭证 |
该接口其实就需要干两件事情:
请求 SSO 进行 token 校验(类似之前的用户, 密码登录, 只不过调用 SSO 平台接口校验);
建立本地会话(和账号密码登录成功之后的建立会话方式一致).
控制校验通过后的页面跳转.
SSO 为子系统提供 token 校验接口
上面讲到 SSO 会暴漏一个 token 校验接口, 这一块逻辑很简单, 就是拿着 token 去 Redis 中查找对应的用户信息是否存在. 众所周知, 小明是一个懒人, 为了投机取巧, 小明 token 的生成规则, 就是 session 的 id, 因此, 判断用户是否登录, 其实就是根据 sessionId 查找 Redis 是否存在会话, 此处有亮点. 通过查看源码, 我发现这个功能, 根本不用我们自己去实现, spring 已经想到我们会用到.
spring 提供了一个接口 org.springframework.session.SessionRepository
- package org.springframework.session;
- public interface SessionRepository<S extends Session> {
- S createSession();
- void save(S var1);
- // 这个就是
- S findById(String var1);
- void deleteById(String var1);
- }
其中 S findById(String var1)就是我们要调用的方法, 这个方法作用就是根据 sessionId 去会话中心查找会话对象, 正是我们所需要的, 我们只需通过 @Autowired 获取, 开箱即用~我们一起看一下业务代码:
- @Autowired
- private SessionRepository sessionRepository;
- @PostMapping("/checkToken")
- public BaseResponseFacade checkToken(@RequestBody UserLoginVo userLoginVo) {
- if (Objects.isNull(userLoginVo)) {
- return ResponseUtil.error(NEED_LOGIN);
- }
- String token = userLoginVo.getToken();
- Session session = sessionRepository.findById(token);
- if (Objects.isNull(session)) {
- return ResponseUtil.error(NEED_LOGIN);
- }
- AdverInfo adverInfo = JSON.parseObject(session.getAttribute("adverInfo"), AdverInfo.class);
- return ResponseUtil.success(adverInfo);
- }
SSO 和子系统的交互文档也贴出来给大家一睹为快
接口调用请求说明
请求方式: POST
请求格式: JSON
请求地址
测试: http:/xxx/checkToken
正式: http://xxx/checkToken
POST 数据示例
- {
- "token": "123456"
- }
参数说明
参数 | 说明 |
---|---|
xxx/checkToken | xxx 为域名,checkToken 为营销云平台提供的校验接口地址 |
token | 调用接口凭据 |
返回说明
正常时返回的 JSON 数据包示例
如果 SSO 校验通过, 则系统 A 可以与与当前用户建立本地会话, 用户正常进入系统
- {
- "data":{
- "XXX":"XXX"
- },
- "errorMsg":"成功",
- "errorCode":0
- }
参数说明
参数 | 说明 |
---|---|
errorCode | 0:表示请求成功 |
errorMsg | 返回码说明 |
XXX | 其他相关信息 |
异常时返回的 JSON 数据包示例
当 SSO 后台校验失败时返回参数如下
- {
- "errorMsg": "NEED_LOGIN",
- "errorCode": 10
- }
参数说明
参数 | 说明 |
---|---|
errorMsg | 错误信息说明 |
errorCode | 错误标志 |
错误码说明
错误码 | 说明 |
---|---|
1 | 系统繁忙 |
10 | 需要用户登录 |
500 | 服务器内部错误 |
来源: https://www.cnblogs.com/coderxx/p/11398712.html