核心配置
Spring Boot 2.x 示例
Spring Boot 2.x为OAuth 2.0登录带来了完全的自动配置功能。
本节展示了如何通过使用 Google 作为认证提供者(Authentication Provider)来配置 OAuth 2.0 Login WebFlux 示例,并涵盖以下主题。
初始设置
要使用谷歌的OAuth 2.0认证系统进行登录,你必须在谷歌API控制台中设置一个项目,以获得OAuth 2.0凭证。
|
谷歌用于认证的 OAuth 2.0实现 符合 OpenID Connect 1.0 规范,并且是 OpenID 认证。 |
按照 OpenID Connect 页面上的指示,从 “Setting up OAuth 2.0” 部分开始。
在完成 “Obtain OAuth 2.0 credentials” 的说明后,你应该有一个新的OAuth客户端,其证书由一个 Client ID 和一个 Client Secret 组成。
设置重定向URI
重定向URI是终端用户的用户代理在通过谷歌认证并被授予访问同意页面上的OAuth客户端(在上一步创建)后,被重定向到应用程序中的路径。
在 “Set a redirect URI” 子部分,确保 Authorized redirect URIs 字段被设置为 localhost:8080/login/oauth2/code/google 。
|
默认重定向URI模板是 |
application.yml 配置
现在你已经有了一个新的谷歌OAuth客户端,你需要配置应用程序,以使用OAuth客户端的认证流程(authentication flow)。要做到这一点:
-
进入
application.yml并设置以下配置Example 1. OAuth Client propertiesspring: security: oauth2: client: registration: (1) google: (2) client-id: google-client-id client-secret: google-client-secret1 spring.security.oauth2.client.registration是OAuth客户端属性的基础属性前缀。2 在基本属性前缀后面是 ClientRegistration, 的ID,如google。 -
将
client-id和client-secret属性中的值替换为你之前创建的OAuth 2.0凭证。
启动 Application
启动Spring Boot 2.x样本,进入 localhost:8080 。然后你会被重定向到默认的自动生成的登录页面,其中显示一个Google的链接。
点击谷歌的链接,然后你会被转到谷歌进行认证。
在用你的谷歌账户凭证进行认证后,呈现在你面前的下一个页面是同意屏幕。同意屏幕要求你允许或拒绝访问你先前创建的OAuth客户端。点击 Allow ,授权OAuth客户端访问你的电子邮件地址和基本个人资料信息。
此时,OAuth客户端从 UserInfo 端点 检索你的电子邮件地址和基本个人资料信息,并建立一个已认证的会话(session)。
Spring Boot 2.x 属性映射
下表列出了Spring Boot 2.x OAuth客户端属性与 ClientRegistration 属性的映射。
| Spring Boot 2.x | ClientRegistration |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
CommonOAuth2Provider
CommonOAuth2Provider 为一些知名的供应商预先定义了一套默认的客户端属性(propertie)。谷歌、GitHub、Facebook和Okta。
例如,authorization-uri、token-uri 和 user-info-uri 对提供者来说不会经常改变。因此,为了减少所需的配置,提供默认值是有意义的。
正如之前所展示的,当我们配置谷歌客户端时,只需要客 client-id 和 client-secret 属性。
下面的列表显示了一个例子。
spring:
security:
oauth2:
client:
registration:
google:
client-id: google-client-id
client-secret: google-client-secret
客户端属性的自动默认在这里工作得天衣无缝,因为 registrationId(google)与 CommonOAuth2Provider 中的 GOOGLE 枚举(不区分大小写)相匹配。
|
对于你可能想指定一个不同的 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
| 1 | registrationId 被设置为 google-login。 |
| 2 | provider 属性被设置为 google,这将利用 CommonOAuth2Provider.GOOGLE.getBuilder() 中设置的客户端属性的自动默认。 |
配置自定义Provider属性
有一些OAuth 2.0提供商支持多租户,这导致每个租户(或子域)的协议端点不同。
例如,在Okta注册的OAuth客户端被分配到一个特定的子域,并有他们自己的协议端点。
对于这些情况,Spring Boot 2.x为配置自定义 provider 属性提供了以下基础属性: 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
| 1 | 基础属性(spring.security.oauth2.client.provider.okta)允许自定义配置协议的端点位置。 |
覆盖 Spring Boot 2.x 自动配置
用于支持OAuth客户端的Spring Boot 2.x自动配置类是 ReactiveOAuth2ClientAutoConfiguration。
它执行以下任务。
-
注册一个
ReactiveClientRegistrationRepository@Bean,由配置的OAuth客户端属性的ClientRegistration组成。 -
注册一个
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 2.x 的 Java 配置
如果你不能使用Spring Boot 2.x,并希望配置 CommonOAuth2Provider 中的一个预定义provider(例如,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()
}
}