最近小明遇到一个需求:需要将几个独立的系统(子系统)汇总到一个集中的系统(父系统)当中,当用户在父系统登录过后,再点击这几个子系统,就可以免登录跳转到任意一个系统。当时一听,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 信息,此外还支持 MONGODBJDBCHAZELCAST 等存储会话方式。通过 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.yml 配置 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.yml 中的配置如下:
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 | 服务器内部错误 |
小明设计的简洁版 sso 就大抵如此,大家可以作为一个 sso 入门 demo 来看待????,如果大家有什么建议或问题,欢迎留言~大家也可以关注微信公众号“程序员小明”获取更多资源~