参考文章:
- 单点登录SSO解决方案之SpringSecurity+JWT实现
SSO原理
参考文章:
- 博客园SSO原理
单点登录SSO是企业级多业务系统整合的常用方案,它的目的是只要在其中的一个业务系统进行登录和授权作用就可以访问其他受信的任业务系统。
一次登录处处登录,一次登出,每个系统都是登出状态,这依赖于一个sso认证中心服务端,每次生成以及验证token的有效性时都需要经过sso服务端。(并且通过redis检验该token是否过期。)
在项目中我们使用JWT来实现这个token的生成和校验。
JWT简介
[[2023-05-07-JWT]]
JWT安全性分析
技术选型
Spring Security + JWT(JSON Web Token)实现单点登录(SSO)及RBAC权限管理理由如下:
- Spring Security 是通用的安全框架,用于处理应用中的身份验证和授权。
- Spring Security OAuth2 是 Spring Security 的扩展模块,专门用于处理 OAuth2 和 OpenID Connect 协议。它可以作为SSO统一权限认证中心的服务器。并且在 Spring Security 的基础上增加了对基于令牌的身份验证和授权机制的支持,特别适合现代分布式系统、微服务架构以及单点登录的场景。
- 从 Spring Security 5.x 版本开始,OAuth2 的支持已经集成到 Spring Security 框架中,不再需要单独使用 Spring Security OAuth2 模块。
什么场景适合
- 微服务架构
- 场景特点:多个微服务应用之间需要相互通信和共享用户身份信息。
- 适用原因:在微服务环境中,每个服务可以通过JWT验证用户身份,避免集中认证服务器成为瓶颈。JWT是无状态的,用户身份信息可以被传递到各个服务中,不依赖单独的会话管理。
- 前后端分离架构
- 场景特点:前后端分离的系统,前端应用通常是SPA(单页应用),而后端提供API。
- 适用原因:前端在登录时通过后端的认证接口获取JWT令牌,之后的每次请求都通过在请求头中带上JWT进行身份验证,无需在后端维护复杂的会话状态。
- 多系统集成
- 场景特点:不同子系统或应用程序之间共享同一个用户数据库,要求用户在一个系统登录后可以访问其他系统而无需再次登录。
- 适用原因:JWT可以在多个系统间传递和验证用户身份,通过将用户身份编码到JWT中,使各系统无缝共享登录状态。
当前项目为什么适合
对于多系统服务、共同使用用户库表的项目使用Spring Security + JWT实现单点登录是一个合适的选择,原因如下:
-
无状态认证:JWT是无状态的,因此不需要在服务端维护复杂的会话状态,也不需要依赖数据库来存储登录会话信息。用户登录成功后,只需要将JWT令牌分发给客户端,客户端每次请求时携带令牌,服务器端只需要验证JWT的合法性即可。
-
跨系统共享认证:多个系统可以通过统一的认证服务(授权服务)颁发JWT,所有系统只需对JWT进行验证即可,实现用户在各系统间的单点登录效果。
-
用户库的集中管理:JWT令牌可以包含用户的权限和角色信息,这样即使多个系统共用一个用户库表,也可以通过JWT中的信息在各个系统中进行不同权限的访问控制。
-
可扩展性:JWT的无状态特性特别适合微服务架构和跨系统服务场景。对于你提到的多系统服务,使用JWT既可以减少服务之间的耦合,也可以使认证流程更加灵活。
改造微服务项目
在多系统集成的项目中,使用 Spring Security + JWT 实现单点登录的确会涉及到在每个系统中引入相关的认证框架,但在实现过程中可以通过适当的设计来降低代码侵入性。
- 需要在每个系统引入 Spring Security + OAuth2 相关框架吗?
是的,每个系统都会需要引入 Spring Security 来实现安全机制和授权验证。如果你选择使用 JWT 作为单点登录的实现方式,以下两种模式可以考虑:
-
集中认证服务(Authorization Server):你可以为整个系统架构设置一个 单独的认证服务,它专门负责用户的身份验证、生成和颁发JWT。这可以通过 Spring Security OAuth2 或其他实现方式(如 Keycloak、Okta 等)来完成。各个系统(微服务)作为资源服务器,只需要验证请求中带的 JWT。
-
每个系统作为资源服务器:每个系统只需负责验证传递过来的 JWT,而不需要处理复杂的登录逻辑。因此,每个系统只需要在请求中通过 Spring Security 检查 JWT 的合法性。
- 每个系统需要编写单点登录相关代码吗?
每个系统在 Spring Security 配置部分的代码需要针对 JWT 做一些简单的配置,但具体的 登录认证逻辑 可以被抽象和集中管理,避免在每个系统中重复实现。这种配置包括:
- JWT 的验证机制:需要在每个系统配置 JWT 的过滤器,处理从请求头中提取 JWT 并验证其合法性。
- 权限和角色解析:从 JWT 中提取用户角色或权限信息,以控制访问。
通常你只需要在每个系统中添加一些配置来集成 JWT 的验证逻辑,而不需要编写与用户认证相关的代码(如登录表单处理等)。
非侵入性实现策略
代码是否有侵入性?
整体来看,Spring Security + JWT 单点登录方案会引入一些侵入性,但主要体现在:
- 安全配置层面:你需要在每个系统中对 Spring Security 进行安全配置,这包括 JWT 的解析、过滤器的配置等。
- 代码侵入性:单点登录方案的实现会对每个系统的安全配置部分有一定侵入性,但这种侵入主要集中在 Spring Security 的配置层面,可以通过合理的抽象和模块化设计,尽量减少业务代码层面的影响。
- 业务代码侵入性较低:只要将认证、权限控制等与业务逻辑解耦(如通过注解 @PreAuthorize
等方式),它对原有的业务代码影响较小。业务代码仍然专注于处理业务功能,而身份验证和授权逻辑在 Spring Security 层进行管理。
- 可以通过创建共享库或模块,将 JWT 验证、解析等通用功能抽象为工具类库,让每个系统直接引入,减少代码的重复和侵入性。
- 通过自定义 Spring Security 的
SecurityConfigurerAdapter
类,统一定义 JWT 过滤器、解析策略等内容,避免直接修改核心业务代码。
总结:
- 需要在每个系统引入框架:每个系统需要引入 Spring Security 来实现安全机制,并且配置 JWT 验证。
- 通过集中管理 JWT 的颁发和验证逻辑,可以将侵入性降到最低,达到单点登录的效果,同时保持代码的简洁和可维护性。
为什么使用redis
Spring Security使用Oauth2、Redis实现SSO单点登录 github: spring-security简单整合reids实现通过jwt单点登录
- 用户下次访问受保护资源的时候,则会携带生成的token,访问前会从redis中读取用户信息,没有读取到则代表未登录。读取到则返回用户信息,存入上下文中。
- 讲一下为啥要使用redis:
- 默认一个项目是将登录认证缓存在session中的,session虽然在内存中,但是被单一项目持有(jvm相关)
- 所以如果我只在项目A进行了登录,那么对于项目B来说我是未登录的,这样就无法实现单点登录,对应也就无法实现微服务和分布式架构了
- 当使用redis将登录认证信息跟唯一token绑定时,只有用户端(比如浏览器的cookie)持有token,那么对所有使用该redis进行认证的应用来说,该用户端都是登录状态
具体实现
参考文章: