1. 概述
本教程将重点介绍如何使用 Spring Security 集成 OpenID Connect (OIDC)。我们将探讨该规范的不同方面,并了解 Spring Security 如何在 OAuth 2.0 客户端中实现这些功能。
2. OpenID Connect 快速入门
OpenID Connect 是构建在 OAuth 2.0 协议之上的身份层。在深入 OIDC 之前,必须先掌握 OAuth 2.0,特别是授权码流程。
OIDC 规范体系非常庞大,包含核心功能和多个可选能力,主要分为以下几部分:
- 核心:身份认证和通过 Claims 传递终端用户信息
- 发现:规定客户端如何动态获取 OpenID 提供商信息
- 动态注册:定义客户端如何向提供商注册
- 会话管理:管理 OIDC 会话的机制
规范中将支持 OIDC 的 OAuth 2.0 认证服务器称为 **OpenID 提供商 (OP)**,使用 OIDC 的 OAuth 2.0 客户端称为 **依赖方 (RP)**。本文将采用这些术语。
⚠️ 客户端可通过在授权请求中添加 openid
scope 来启用此扩展。OP 会将终端用户信息封装在称为 ID Token 的 JWT 中返回。
3. 项目搭建
在开始开发前,需先向 OpenID 提供商注册 OAuth 2.0 客户端。本教程以 Google 作为 OP,可参考官方指南注册应用。注意 openid
scope 默认已启用。
注册时设置的回调 URI 为:http://localhost:8081/login/oauth2/code/google
。完成后将获得 Client ID 和 Client Secret。
3.1. Maven 配置
在 pom.xml
中添加依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
<version>2.2.6.RELEASE</version>
</dependency>
该 starter 包含:
-
spring-security-oauth2-client
:OAuth 2.0 登录和客户端功能 - JOSE 库:JWT 支持
✅ 可通过 Maven Central 查找最新版本。
4. 基于 Spring Boot 的基础配置
Spring Boot 让配置变得极其简单,只需定义两个属性:
spring:
security:
oauth2:
client:
registration:
google:
client-id: your-google-client-id
client-secret: your-google-secret
启动应用后访问受保护接口,会自动跳转到 Google 登录页面。看似简单,但底层实现相当复杂。接下来我们分析 Spring Security 如何实现这一过程。
Google 是知名提供商,Spring 已预定义其配置,可在 CommonOAuth2Provider
枚举中查看,包括:
- 默认 scope
- 授权接口
- 令牌接口
- 用户信息接口(OIDC 核心规范的一部分)
4.1. 访问用户信息
Spring Security 提供 OidcUser
实体表示 OIDC 用户主体,除基础方法外还支持:
- 获取 ID Token 及其 Claims
- 获取用户信息接口返回的 Claims
- 合并两组 Claims
在控制器中可直接获取:
@GetMapping("/oidc-principal")
public OidcUser getOidcUserPrincipal(
@AuthenticationPrincipal OidcUser principal) {
return principal;
}
或在 Bean 中通过 SecurityContextHolder
获取:
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication.getPrincipal() instanceof OidcUser) {
OidcUser principal = ((OidcUser) authentication.getPrincipal());
// ...
}
检查 principal 会发现用户名、邮箱、头像和地区等实用信息。
5. OIDC 实战
目前我们已实现 OIDC 登录,但尚未接触 OIDC 特定细节——Spring 已替我们完成大部分工作。接下来深入分析底层实现,以便充分利用该规范。
5.1. 登录流程
启用 RestTemplate
日志观察请求:
logging:
level:
org.springframework.web.client.RestTemplate: DEBUG
访问受保护接口时,会看到标准的 OAuth 2.0 授权码流程,但存在 OIDC 特有差异:
1. 用户信息接口调用
若配置的 scope 包含 profile
、email
、address
或 phone
,Spring 会调用用户信息接口获取额外数据。但 Google 使用自定义 scope(https://www.googleapis.com/auth/userinfo.email
和 https://www.googleapis.com/auth/userinfo.profile
),因此 Spring 不会调用该接口。
所有信息均来自 ID Token。可通过自定义 OidcUserService
适配此行为:
@Configuration
public class OAuth2LoginSecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
Set<String> googleScopes = new HashSet<>();
googleScopes.add("https://www.googleapis.com/auth/userinfo.email");
googleScopes.add("https://www.googleapis.com/auth/userinfo.profile");
OidcUserService googleUserService = new OidcUserService();
googleUserService.setAccessibleScopes(googleScopes);
http.authorizeHttpRequests(authorizeRequests -> authorizeRequests.anyRequest().authenticated())
.oauth2Login(oauthLogin -> oauthLogin.userInfoEndpoint(userInfoEndpointConfig ->
userInfoEndpointConfig.oidcUserService(googleUserService)));
return http.build();
}
}
2. JWK Set URI 调用
用于验证 ID Token 的签名(参考 JWS 和 JWK)。
5.2. ID Token
在授权码流程中,ID Token 与 Access Token 一起从令牌接口返回。OidcUser
包含 ID Token 的 Claims 和原始 JWT(可通过 jwt.io 检查)。
Spring 提供便捷方法获取标准 Claims,ID Token 必须包含:
- 发行方标识符(URL 格式,如
https://accounts.google.com
) - 主体标识符(终端用户在 OP 的唯一标识)
- 过期时间
- 签发时间
- 受众(包含配置的客户端 ID)
还包含标准 Claims(如 name
、locale
、picture
、email
)。
这些标准字段简化了跨提供商的开发工作。
5.3. Claims 与 Scopes
OP 返回的 Claims 取决于请求的 scope。OIDC 定义的标准 scope 包括:
-
profile
:请求基础信息(如name
、preferred_username
、picture
) -
email
:获取email
和email_verified
-
address
-
phone
:获取phone_number
和phone_number_verified
⚠️ 规范允许在授权请求中指定单个 Claim,但 Spring 尚未支持此功能。
6. Spring 对 OIDC 发现的支持
OIDC 发现机制允许 RP 动态获取 OP 信息。OP 需在 /.well-known/openid-configuration
提供标准元数据 JSON 文档。
Spring 支持通过发行方 URI 自动配置:
spring:
security:
oauth2:
client:
registration:
custom-google:
client-id: your-google-client-id
client-secret: your-google-secret
provider:
custom-google:
issuer-uri: https://accounts.google.com
重启应用后,日志会显示启动时调用了 openid-configuration
接口。可直接访问 https://accounts.google.com/.well-known/openid-configuration 查看元数据(如授权接口、令牌接口、支持的 scope 等)。
❌ 若发现接口在启动时不可用,应用将无法成功启动。
7. OpenID Connect 会话管理
会话管理规范定义了:
- 监控终端用户在 OP 的登录状态
- 向 OP 注册 RP 登出 URI
- 通知 OP 终端用户已登出 RP(可能需同步登出 OP)
本教程重点实现 RP 发起的登出。
7.1. OpenID 提供商配置
以 Okta 为例(参考官方指南),Spring 默认回调 URI 为 /login/oauth2/code/okta
。
应用配置:
spring:
security:
oauth2:
client:
registration:
okta:
client-id: your-okta-client-id
client-secret: your-okta-secret
provider:
okta:
issuer-uri: https://dev-123.okta.com
OP 的登出接口在发现文档的 end_session_endpoint
字段中定义。
7.2. LogoutSuccessHandler 配置
自定义登出逻辑:
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(authorizeRequests -> authorizeRequests
.requestMatchers("/home").permitAll()
.anyRequest().authenticated())
.oauth2Login(AbstractAuthenticationFilterConfigurer::permitAll)
.logout(logout -> logout.logoutSuccessHandler(oidcLogoutSuccessHandler()));
return http.build();
}
使用 Spring 的 OidcClientInitiatedLogoutSuccessHandler
:
@Autowired
private ClientRegistrationRepository clientRegistrationRepository;
private LogoutSuccessHandler oidcLogoutSuccessHandler() {
OidcClientInitiatedLogoutSuccessHandler oidcLogoutSuccessHandler =
new OidcClientInitiatedLogoutSuccessHandler(
this.clientRegistrationRepository);
oidcLogoutSuccessHandler.setPostLogoutRedirectUri(
URI.create("http://localhost:8081/home"));
return oidcLogoutSuccessHandler;
}
⚠️ 需在 OP 客户端配置中将 http://localhost:8081/home
添加为有效的登出重定向 URI。
执行流程:
- 调用
/logout
接口 - 重定向到 OP 的登出接口
- 最终跳转至配置的 URI
下次访问受保护接口时,必须重新登录 OP。
8. 总结
本文深入探讨了 OpenID Connect 的解决方案及 Spring Security 的实现方式。完整示例代码可在 GitHub 获取。