核心配置
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-secret
1 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()
}
}