核心配置
Spring Boot 示例
Spring Boot 為 OAuth 2.0 登入提供了完整的自動配置能力。
本節展示瞭如何使用 Google 作為 Authentication Provider 配置 OAuth 2.0 Login WebFlux 示例,並涵蓋以下主題:
初始設定
要使用 Google 的 OAuth 2.0 認證系統進行登入,您必須在 Google API Console 中設定一個專案以獲取 OAuth 2.0 憑據。
Google 的 OAuth 2.0 認證實現 符合 OpenID Connect 1.0 規範,並且已獲得 OpenID 認證。 |
按照 OpenID Connect 頁面上的說明進行操作,從“設定 OAuth 2.0”部分開始。
完成“獲取 OAuth 2.0 憑據”說明後,您應該會擁有一個新的 OAuth 客戶端,其憑據包括一個 Client ID 和一個 Client Secret。
設定重定向 URI
重定向 URI 是應用中的一個路徑,終端使用者的使用者代理在 Google 認證並被授權訪問同意頁面上的 OAuth 客戶端(在前一步建立的)後,會被重定向回該路徑。
在“設定重定向 URI”小節中,確保 Authorized redirect URIs 欄位設定為 localhost:8080/login/oauth2/code/google
。
預設的重定向 URI 模板是 |
配置 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-secret
1 spring.security.oauth2.client.registration
是 OAuth 客戶端屬性的基礎屬性字首。2 基礎屬性字首後面是 ClientRegistration
的 ID,例如 google。 -
將
client-id
和client-secret
屬性中的值替換為您之前建立的 OAuth 2.0 憑據。
啟動應用
啟動 Spring Boot 示例並訪問 localhost:8080
。然後您會被重定向到預設的自動生成的登入頁面,該頁面顯示一個指向 Google 的連結。
點選 Google 連結,您將重定向到 Google 進行認證。
使用您的 Google 賬戶憑據認證後,接下來顯示的頁面是 Consent 頁面。Consent 頁面要求您允許或拒絕訪問您之前建立的 OAuth 客戶端。點選 Allow 授權 OAuth 客戶端訪問您的電子郵件地址和基本個人資料資訊。
此時,OAuth 客戶端從 UserInfo Endpoint 獲取您的電子郵件地址和基本個人資料資訊,並建立一個認證會話。
Spring Boot 屬性對映
下表概述了 Spring Boot OAuth 客戶端屬性到 ClientRegistration 屬性的對映關係。
Spring Boot | ClientRegistration |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
可以透過發現 OpenID Connect Provider 的配置端點或 Authorization Server 的元資料端點,透過指定 spring.security.oauth2.client.provider.[providerId].issuer-uri 屬性來初步配置 ClientRegistration。 |
CommonOAuth2Provider
CommonOAuth2Provider
為許多知名提供商預定義了一組預設客戶端屬性:Google, GitHub, Facebook 和 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
1 | registrationId 設定為 google-login 。 |
2 | 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
1 | 基礎屬性(spring.security.oauth2.client.provider.okta )允許對協議端點位置進行自定義配置。 |
覆蓋 Spring Boot 自動配置
Spring Boot OAuth 客戶端支援的自動配置類是 ReactiveOAuth2ClientAutoConfiguration
。
它執行以下任務:
-
註冊一個由配置的 OAuth 客戶端屬性組成的
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://#/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://#/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://#/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://#/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()
}
}