基于 Spring Security OAuth2 + JWT 实现 SSO
单点登录(Single Sign-On,SSO)是一种身份认证机制,允许用户只需登录一次,即可访问多个相互信任的应用系统,无需重复登录。在分布式系统中,SSO 是提升用户体验和系统安全性的核心方案。
SSO 核心原理
- 认证中心(Identity Provider,IdP):统一的登录入口,负责用户身份验证并颁发令牌(如 JWT)。
- 服务提供商(Service Provider,SP):各个业务系统,信任认证中心颁发的令牌,无需重复验证用户身份。
- 令牌机制:认证中心验证用户身份后,生成加密令牌(如 JWT),用户访问其他系统时携带令牌,SP 验证令牌有效性即可信任用户。
基于 Spring Security OAuth2 + JWT 实现 SSO
结合前面的 OAuth2 + JWT 整合方案,扩展实现 SSO 的核心步骤如下:
1. 架构设计
- 认证中心(Authorization Server):独立部署,负责用户登录、令牌颁发(JWT)、令牌验证。
- 多个资源服务器(Resource Server):各业务系统(如订单系统、用户系统),配置信任认证中心的 JWT 令牌。
- 共享会话:通过 JWT 令牌在各系统间共享用户身份信息(无需共享 Session)。
2. 认证中心配置(关键扩展)
认证中心需支持授权码模式(authorization_code)(适合 SSO 场景,安全性高),并确保 JWT 令牌可被所有 SP 验证。
@Configuration
@EnableAuthorizationServer
public class SsoAuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {@Autowiredprivate AuthenticationManager authenticationManager;@Autowiredprivate UserDetailsService userDetailsService;@Autowiredprivate JwtTokenUtil jwtTokenUtil;// 配置客户端(各SP系统需在认证中心注册)@Overridepublic void configure(ClientDetailsServiceConfigurer clients) throws Exception {clients.inMemory()// 客户端1(如订单系统).withClient("order-service").secret(passwordEncoder().encode("order-secret")).authorizedGrantTypes("authorization_code", "refresh_token") // 授权码模式.redirectUris("http://localhost:8081/login/oauth2/code/order-service") // 回调地址(SP的地址).scopes("read", "write").accessTokenValiditySeconds(3600)// 客户端2(如用户系统).and().withClient("user-service").secret(passwordEncoder().encode("user-secret")).authorizedGrantTypes("authorization_code", "refresh_token").redirectUris("http://localhost:8082/login/oauth2/code/user-service").scopes("read", "write").accessTokenValiditySeconds(3600);}// JWT令牌转换器(确保所有SP使用相同密钥验证)@Beanpublic JwtAccessTokenConverter accessTokenConverter() {JwtAccessTokenConverter converter = new JwtAccessTokenConverter();converter.setSigningKey(jwtTokenUtil.SECRET); // 所有系统共享的签名密钥return converter;}// 令牌存储(JWT)@Beanpublic TokenStore tokenStore() {return new JwtTokenStore(accessTokenConverter());}@Overridepublic void configure(AuthorizationServerEndpointsConfigurer endpoints) {endpoints.authenticationManager(authenticationManager).userDetailsService(userDetailsService).tokenStore(tokenStore()).accessTokenConverter(accessTokenConverter());}// 允许跨域(可选,根据SP部署情况配置)@Overridepublic void configure(AuthorizationServerSecurityConfigurer security) {security.tokenKeyAccess("permitAll()") // 允许获取公钥(用于验证JWT).checkTokenAccess("isAuthenticated()");}@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}
}
3. 服务提供商(SP)配置
每个业务系统(SP)需配置为资源服务器,并信任认证中心的 JWT 令牌。
以order-service
(端口 8081)为例:
java
运行
@Configuration
@EnableResourceServer
@EnableWebSecurity
public class OrderServiceConfig extends ResourceServerConfigurerAdapter {@Autowiredprivate JwtTokenUtil jwtTokenUtil;// 资源ID(需与认证中心客户端配置对应)private static final String RESOURCE_ID = "order-service";@Overridepublic void configure(ResourceServerSecurityConfigurer resources) {resources.resourceId(RESOURCE_ID).tokenStore(tokenStore()) // 使用JWT验证令牌.stateless(true); // 无状态(不存储Session)}// 配置JWT令牌验证@Beanpublic TokenStore tokenStore() {JwtAccessTokenConverter converter = new JwtAccessTokenConverter();converter.setSigningKey(jwtTokenUtil.SECRET); // 与认证中心共享密钥return new JwtTokenStore(converter);}// 配置访问规则@Overridepublic void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/public/**").permitAll().anyRequest().authenticated().and().csrf().disable(); // 前后端分离场景可禁用CSRF}
}
4. SSO 登录流程
用户访问 SP:用户访问
http://localhost:8081/order
(订单系统),未登录则被重定向到认证中心。认证中心登录:重定向至认证中心登录页
http://localhost:8080/oauth/authorize?client_id=order-service&response_type=code&redirect_uri=http://localhost:8081/login/oauth2/code/order-service
,用户输入账号密码。颁发授权码:认证中心验证通过后,生成授权码(code),并重定向回 SP 的回调地址(如
http://localhost:8081/login/oauth2/code/order-service?code=xxx
)。SP 获取令牌:SP 使用授权码向认证中心请求令牌:
POST http://localhost:8080/oauth/token Content-Type: application/x-www-form-urlencoded Authorization: Basic b3JkZXItc2VydmljZTpvcmRlci1zZWNyZXQ=grant_type=authorization_code&code=xxx&redirect_uri=http://localhost:8081/login/oauth2/code/order-service
认证中心返回 JWT 格式的
access_token
。SP 验证令牌:SP 解析 JWT 令牌,获取用户信息,允许访问资源。
访问其他 SP:用户访问
http://localhost:8082/user
(用户系统)时,同样重定向到认证中心,但认证中心发现用户已登录,直接颁发授权码,用户无需重复输入密码。
5. 关键优化
- 统一登录页:认证中心提供统一的登录界面,保持用户体验一致。
- 令牌刷新:通过
refresh_token
机制,避免用户频繁登录。 - 单点登出:认证中心需提供登出接口,清除用户会话,并通知所有 SP 失效令牌(可选,实现复杂,通常通过令牌过期机制简化)。
- HTTPS 传输:所有步骤必须使用 HTTPS,防止令牌和授权码被窃取。
与传统 Session 共享的区别
方案 | 原理 | 优点 | 缺点 |
---|---|---|---|
传统 Session 共享 | 多系统共享同一个 Session 存储 | 实现简单(如 Redis 共享 Session) | 耦合性高,分布式场景扩展性差 |
OAuth2 + JWT SSO | 基于令牌的无状态认证 | 松耦合,支持跨域、跨语言,扩展性强 | 令牌需加密,登出实现复杂 |
总结
基于 Spring Security OAuth2 + JWT 的 SSO 方案,通过认证中心统一颁发 JWT 令牌,各服务提供商验证令牌有效性,实现了 “一次登录,多系统访问” 的目标。核心是确保令牌安全(签名 + HTTPS) 和各系统信任统一的认证中心。
实际项目中,可结合 Spring Cloud Security 简化配置,并使用 Redis 存储令牌黑名单(用于主动登出)。
为什么不建议直接用JWT实现单点登陆?
使用 JWT 实现单点登录(SSO)有其优势,但也存在局限性,是否 “好” 取决于具体场景。以下从 JWT 实现 SSO 的特点、适用场景,以及更优的 SSO 技术方案两方面展开分析:
一、JWT 实现单点登录的优缺点
优点:
- 无状态:JWT 自身包含用户身份信息,服务端无需存储会话状态,减轻了 SSO 服务器的存储压力,适合分布式系统。
- 跨域友好:JWT 通常通过 HTTP 头部(如 Authorization)传递,天然支持跨域场景,无需依赖 Cookie 的同源限制。
- 实现简单:无需复杂的会话同步机制,客户端携带 JWT 即可完成身份验证,开发成本较低。
缺点:
- 无法即时吊销:JWT 一旦签发,在有效期内无法主动废除(除非结合黑名单机制,但会破坏无状态特性),若用户注销或令牌泄露,风险会持续到过期。
- 安全性依赖签名:需确保签名密钥安全,一旦泄露,攻击者可伪造令牌;且 JWT payload 是 Base64 编码(非加密),敏感信息需额外加密。
- 令牌体积可能过大:若携带过多用户信息,会增加请求开销,影响性能。
二、更推荐的单点登录技术方案
单点登录的核心是 “一次认证,多系统信任”,技术选型需结合安全性、可扩展性、实时性等需求,主流方案如下:
1. OAuth 2.0 + OpenID Connect(OIDC)
- 适用场景:第三方登录(如微信、GitHub 登录)、企业内部多系统 SSO、需要细粒度权限控制的场景。
- 优势:
- OIDC 在 OAuth 2.0 基础上增加了身份层,通过 ID Token(JWT 格式)标准化用户身份信息,同时支持 Access Token 控制资源访问。
- 支持多种授权模式(如授权码模式、密码模式),安全性高,适合复杂场景。
- 可通过令牌撤销端点(Token Revocation)主动吊销令牌,解决 JWT 无法即时失效的问题。
- 缺点:协议较复杂,实现成本较高。
2. 基于 Session + 中央认证服务器(CAS)
- 适用场景:企业内部系统(如办公系统、ERP 等),对安全性和实时性要求高,且系统技术栈统一(如均为 Web 应用)。
- 优势:
- 经典的 SSO 方案,流程成熟:用户在 CAS 服务器登录后,各子系统通过 CAS 验证票据(Ticket)获取身份,会话集中管理。
- 支持即时注销:注销时 CAS 服务器可通知所有子系统销毁本地会话,安全性强。
- 缺点:依赖 Cookie 传递会话,跨域支持较弱;子系统需与 CAS 服务器交互验证票据,可能增加网络开销。
3. JWT + 黑名单机制(折中方案)
- 适用场景:对无状态要求高、用户规模不大、令牌有效期短(如 15 分钟)的场景(如移动端 API 服务)。
- 优化点:通过 Redis 维护已吊销的 JWT 黑名单,验证时先检查黑名单,平衡无状态和安全性,但会增加服务端少量存储和查询成本。
三、总结
- 不推荐单独用 JWT 做 SSO:主要问题是无法即时吊销,安全性风险较高,仅适合低安全需求、短令牌有效期的场景。
- 首选方案:OAuth 2.0 + OIDC,通用性强、安全性高,支持第三方登录和复杂权限控制,是目前行业主流的 SSO 标准(如 Google、Facebook 登录均基于此)。
- 备选方案:若为企业内部封闭系统,CAS 更简单可控;若需无状态且能接受黑名单开销,可考虑 JWT + 黑名单。