核心配置
Spring Boot 示例
Spring Boot 为 OAuth 2.0 登录提供了完整的自动配置功能。
本节展示了如何使用 Google 作为 身份验证提供者 来配置 OAuth 2.0 Login WebFlux 示例,并涵盖以下主题:
初始设置
要使用 Google 的 OAuth 2.0 认证系统进行登录,您必须在 Google API 控制台中设置一个项目以获取 OAuth 2.0 凭据。
Google 的 OAuth 2.0 实现 用于身份验证,它符合 OpenID Connect 1.0 规范,并且通过了 OpenID 认证。
请按照 OpenID Connect 页面上的说明操作,从“设置 OAuth 2.0”部分开始。
完成“获取 OAuth 2.0 凭据”的步骤后,您将获得一个新的 OAuth 客户端,其凭据包含一个客户端 ID 和一个客户端密钥。
设置重定向URI
重定向URI是终端用户在Google完成身份验证并在同意页面上获得对OAuth客户端(在上一步中创建)的访问权限后,其用户代理被重定向回应用程序的路径。
在“设置重定向 URI”子部分中,请确保 Authorized redirect URIs 字段设置为 [localhost:8080/login/oauth2/code/google](http://localhost:8080/login/oauth2/code/google)。
默认的重定向URI模板为 {baseUrl}/login/oauth2/code/{registrationId}。其中,registrationId 是 ClientRegistration 的唯一标识符。在我们的示例中,registrationId 是 google。
配置 application.yml
现在您已拥有一个新的 Google OAuth 客户端,接下来需要配置应用程序以使用该 OAuth 客户端进行身份验证流程。为此:
-
前往
application.yml文件并设置以下配置:示例 1. OAuth 客户端属性
spring:
security:
oauth2:
client:
registration: // <1>
google: // <2>
client-id: google-client-id
client-secret: google-client-secretspring.security.oauth2.client.registration是 OAuth 客户端属性的基础属性前缀。基础属性前缀之后是 ClientRegistration 的 ID,例如 google。
-
将
client-id和client-secret属性中的值替换为您之前创建的 OAuth 2.0 凭据。
启动应用程序
启动Spring Boot示例并访问[localhost:8080](http://localhost:8080)。随后您将被重定向到默认的自动生成登录页面,该页面会显示Google的登录链接。
点击Google链接,您将被重定向至Google进行身份验证。
使用您的谷歌账户凭据完成身份验证后,接下来显示的页面是授权同意界面。该界面会询问您是否允许之前创建的OAuth客户端访问您的数据。点击 允许 以授权该OAuth客户端访问您的电子邮件地址和基本个人资料信息。
此时,OAuth 客户端会从用户信息端点获取您的电子邮件地址和基本个人资料信息,并建立一个经过身份验证的会话。
Spring Boot 属性映射
下表概述了 Spring Boot OAuth Client 属性与 ClientRegistration 属性的映射关系。
| Spring Boot | ClientRegistration |
|---|---|
spring.security.oauth2.client.registration._[registrationId]_ | registrationId |
spring.security.oauth2.client.registration._[registrationId]_.client-id | clientId |
spring.security.oauth2.client.registration._[registrationId]_.client-secret | clientSecret |
spring.security.oauth2.client.registration._[registrationId]_.client-authentication-method | clientAuthenticationMethod |
spring.security.oauth2.client.registration._[registrationId]_.authorization-grant-type | authorizationGrantType |
spring.security.oauth2.client.registration._[registrationId]_.redirect-uri | redirectUri |
spring.security.oauth2.client.registration._[registrationId]_.scope | scopes |
spring.security.oauth2.client.registration._[registrationId]_.client-name | clientName |
spring.security.oauth2.client.provider._[providerId]_.authorization-uri | providerDetails.authorizationUri |
spring.security.oauth2.client.provider._[providerId]_.token-uri | providerDetails.tokenUri |
spring.security.oauth2.client.provider._[providerId]_.jwk-set-uri | providerDetails.jwkSetUri |
spring.security.oauth2.client.provider._[providerId]_.issuer-uri | providerDetails.issuerUri |
spring.security.oauth2.client.provider._[providerId]_.user-info-uri | providerDetails.userInfoEndpoint.uri |
spring.security.oauth2.client.provider._[providerId]_.user-info-authentication-method | providerDetails.userInfoEndpoint.authenticationMethod |
spring.security.oauth2.client.provider._[providerId]_.user-name-attribute | providerDetails.userInfoEndpoint.userNameAttributeName |
CommonOAuth2Provider
CommonOAuth2Provider 为多个知名提供商预定义了一组默认客户端属性:Google、GitHub、Facebook、X 和 Okta。
例如,对于某个提供商而言,authorization-uri、token-uri 和 user-info-uri 通常不会频繁变动。因此,提供默认值以减少必要的配置是合理的做法。
如前所述,当我们配置Google客户端时,仅需提供client-id和client-secret属性。
以下清单展示了一个示例:
spring:
security:
oauth2:
client:
registration:
google:
client-id: google-client-id
client-secret: google-client-secret
客户端属性的自动默认设置在这里无缝工作,因为 registrationId (google) 与 CommonOAuth2Provider 中的 GOOGLE enum(不区分大小写)匹配。
对于需要指定不同 registrationId(例如 google-login)的场景,您仍可通过配置 provider 属性来利用客户端属性的自动默认值机制。
以下清单展示了一个示例:
spring:
security:
oauth2:
client:
registration:
google-login: // <1>
provider: google // <2>
client-id: google-client-id
client-secret: google-client-secret
registrationId被设置为google-login。provider属性被设置为google,这将利用在CommonOAuth2Provider.GOOGLE.getBuilder()中设置的客户端属性的自动默认值。
配置自定义提供程序属性
一些 OAuth 2.0 提供商支持多租户功能,这会导致每个租户(或子域名)拥有不同的协议端点。
例如,一个在Okta注册的OAuth客户端会被分配到一个特定的子域,并拥有其专属的协议端点。
对于这些情况,Spring Boot 提供了以下基础属性来配置自定义提供商的属性:spring.security.oauth2.client.provider._[providerId]_。
以下清单展示了一个示例:
spring:
security:
oauth2:
client:
registration:
okta:
client-id: okta-client-id
client-secret: okta-client-secret
provider:
okta: // <1>
authorization-uri: https://your-subdomain.oktapreview.com/oauth2/v1/authorize
token-uri: https://your-subdomain.oktapreview.com/oauth2/v1/token
user-info-uri: https://your-subdomain.oktapreview.com/oauth2/v1/userinfo
user-name-attribute: sub
jwk-set-uri: https://your-subdomain.oktapreview.com/oauth2/v1/keys
基础属性 (
spring.security.oauth2.client.provider.okta) 允许自定义协议端点位置的配置。
覆盖 Spring Boot 自动配置
Spring Boot 中用于支持 OAuth 客户端的自动配置类是 ReactiveOAuth2ClientAutoConfiguration。
它执行以下任务:
- 注册一个由配置的 OAuth 客户端属性中的
ClientRegistration组成的ReactiveClientRegistrationRepository@Bean。 - 注册一个
SecurityWebFilterChain@Bean,并通过serverHttpSecurity.oauth2Login()启用 OAuth 2.0 登录。
如果您需要根据特定需求覆盖自动配置,可以通过以下方式实现:
注册一个 ReactiveClientRegistrationRepository @Bean
以下示例展示了如何注册一个 ReactiveClientRegistrationRepository @Bean:
- Java
- Kotlin
@Configuration
public class OAuth2LoginConfig {
@Bean
public ReactiveClientRegistrationRepository clientRegistrationRepository() {
return new InMemoryReactiveClientRegistrationRepository(this.googleClientRegistration());
}
private ClientRegistration googleClientRegistration() {
return ClientRegistration.withRegistrationId("google")
.clientId("google-client-id")
.clientSecret("google-client-secret")
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.redirectUri("{baseUrl}/login/oauth2/code/{registrationId}")
.scope("openid", "profile", "email", "address", "phone")
.authorizationUri("https://accounts.google.com/o/oauth2/v2/auth")
.tokenUri("https://www.googleapis.com/oauth2/v4/token")
.userInfoUri("https://www.googleapis.com/oauth2/v3/userinfo")
.userNameAttributeName(IdTokenClaimNames.SUB)
.jwkSetUri("https://www.googleapis.com/oauth2/v3/certs")
.clientName("Google")
.build();
}
}
@Configuration
class OAuth2LoginConfig {
@Bean
fun clientRegistrationRepository(): ReactiveClientRegistrationRepository {
return InMemoryReactiveClientRegistrationRepository(googleClientRegistration())
}
private fun googleClientRegistration(): ClientRegistration {
return ClientRegistration.withRegistrationId("google")
.clientId("google-client-id")
.clientSecret("google-client-secret")
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.redirectUri("{baseUrl}/login/oauth2/code/{registrationId}")
.scope("openid", "profile", "email", "address", "phone")
.authorizationUri("https://accounts.google.com/o/oauth2/v2/auth")
.tokenUri("https://www.googleapis.com/oauth2/v4/token")
.userInfoUri("https://www.googleapis.com/oauth2/v3/userinfo")
.userNameAttributeName(IdTokenClaimNames.SUB)
.jwkSetUri("https://www.googleapis.com/oauth2/v3/certs")
.clientName("Google")
.build()
}
}
注册一个 SecurityWebFilterChain @Bean
以下示例展示了如何使用 @EnableWebFluxSecurity 注册一个 SecurityWebFilterChain @Bean,并通过 serverHttpSecurity.oauth2Login() 启用 OAuth 2.0 登录:
- Java
- Kotlin
@Configuration
@EnableWebFluxSecurity
public class OAuth2LoginSecurityConfig {
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange((authorize) -> authorize
.anyExchange().authenticated()
)
.oauth2Login(withDefaults());
return http.build();
}
}
@Configuration
@EnableWebFluxSecurity
class OAuth2LoginSecurityConfig {
@Bean
fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
http {
authorizeExchange {
authorize(anyExchange, authenticated)
}
oauth2Login { }
}
return http.build()
}
}
完全覆盖自动配置
以下示例展示了如何通过注册一个 ReactiveClientRegistrationRepository @Bean 和一个 SecurityWebFilterChain @Bean 来完全覆盖自动配置。
- Java
- Kotlin
@Configuration
@EnableWebFluxSecurity
public class OAuth2LoginConfig {
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange((authorize) -> authorize
.anyExchange().authenticated()
)
.oauth2Login(withDefaults());
return http.build();
}
@Bean
public ReactiveClientRegistrationRepository clientRegistrationRepository() {
return new InMemoryReactiveClientRegistrationRepository(this.googleClientRegistration());
}
private ClientRegistration googleClientRegistration() {
return ClientRegistration.withRegistrationId("google")
.clientId("google-client-id")
.clientSecret("google-client-secret")
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.redirectUri("{baseUrl}/login/oauth2/code/{registrationId}")
.scope("openid", "profile", "email", "address", "phone")
.authorizationUri("https://accounts.google.com/o/oauth2/v2/auth")
.tokenUri("https://www.googleapis.com/oauth2/v4/token")
.userInfoUri("https://www.googleapis.com/oauth2/v3/userinfo")
.userNameAttributeName(IdTokenClaimNames.SUB)
.jwkSetUri("https://www.googleapis.com/oauth2/v3/certs")
.clientName("Google")
.build();
}
}
@Configuration
@EnableWebFluxSecurity
class OAuth2LoginConfig {
@Bean
fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
http {
authorizeExchange {
authorize(anyExchange, authenticated)
}
oauth2Login { }
}
return http.build()
}
@Bean
fun clientRegistrationRepository(): ReactiveClientRegistrationRepository {
return InMemoryReactiveClientRegistrationRepository(googleClientRegistration())
}
private fun googleClientRegistration(): ClientRegistration {
return ClientRegistration.withRegistrationId("google")
.clientId("google-client-id")
.clientSecret("google-client-secret")
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.redirectUri("{baseUrl}/login/oauth2/code/{registrationId}")
.scope("openid", "profile", "email", "address", "phone")
.authorizationUri("https://accounts.google.com/o/oauth2/v2/auth")
.tokenUri("https://www.googleapis.com/oauth2/v4/token")
.userInfoUri("https://www.googleapis.com/oauth2/v3/userinfo")
.userNameAttributeName(IdTokenClaimNames.SUB)
.jwkSetUri("https://www.googleapis.com/oauth2/v3/certs")
.clientName("Google")
.build()
}
}
不使用 Spring Boot 的 Java 配置
如果你无法使用 Spring Boot,并希望配置 CommonOAuth2Provider 中预定义的某个提供程序(例如 Google),请应用以下配置:
- Java
- Kotlin
@Configuration
@EnableWebFluxSecurity
public class OAuth2LoginConfig {
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange((authorize) -> authorize
.anyExchange().authenticated()
)
.oauth2Login(withDefaults());
return http.build();
}
@Bean
public ReactiveClientRegistrationRepository clientRegistrationRepository() {
return new InMemoryReactiveClientRegistrationRepository(this.googleClientRegistration());
}
@Bean
public ReactiveOAuth2AuthorizedClientService authorizedClientService(
ReactiveClientRegistrationRepository clientRegistrationRepository) {
return new InMemoryReactiveOAuth2AuthorizedClientService(clientRegistrationRepository);
}
@Bean
public ServerOAuth2AuthorizedClientRepository authorizedClientRepository(
ReactiveOAuth2AuthorizedClientService authorizedClientService) {
return new AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository(authorizedClientService);
}
private ClientRegistration googleClientRegistration() {
return CommonOAuth2Provider.GOOGLE.getBuilder("google")
.clientId("google-client-id")
.clientSecret("google-client-secret")
.build();
}
}
@Configuration
@EnableWebFluxSecurity
class OAuth2LoginConfig {
@Bean
fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
http {
authorizeExchange {
authorize(anyExchange, authenticated)
}
oauth2Login { }
}
return http.build()
}
@Bean
fun clientRegistrationRepository(): ReactiveClientRegistrationRepository {
return InMemoryReactiveClientRegistrationRepository(googleClientRegistration())
}
@Bean
fun authorizedClientService(
clientRegistrationRepository: ReactiveClientRegistrationRepository
): ReactiveOAuth2AuthorizedClientService {
return InMemoryReactiveOAuth2AuthorizedClientService(clientRegistrationRepository)
}
@Bean
fun authorizedClientRepository(
authorizedClientService: ReactiveOAuth2AuthorizedClientService
): ServerOAuth2AuthorizedClientRepository {
return AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository(authorizedClientService)
}
private fun googleClientRegistration(): ClientRegistration {
return CommonOAuth2Provider.GOOGLE.getBuilder("google")
.clientId("google-client-id")
.clientSecret("google-client-secret")
.build()
}
}