核心設定
Spring Boot 範例
Spring Boot 為 OAuth 2.0 登入帶來完整自動設定功能。
本節說明如何使用 Google 作為身份驗證提供者來設定 OAuth 2.0 登入 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 用戶端,其憑證包含用戶端 ID 和用戶端密碼。
設定重新導向 URI
重新導向 URI 是應用程式中的路徑,使用者代理程式在使用者通過 Google 身份驗證並在同意頁面上被授予 OAuth 用戶端 (在上一步中建立) 的存取權後,會重新導向回該路徑。
在「設定重新導向 URI」子章節中,請確保 已授權的重新導向 URI 欄位設定為 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 帳戶憑證完成身份驗證後,下一個顯示給您的頁面是同意畫面。同意畫面會詢問您是否允許或拒絕存取您先前建立的 OAuth 用戶端。按一下 允許 以授權 OAuth 用戶端存取您的電子郵件地址和基本個人資料資訊。
此時,OAuth 用戶端會從 UserInfo 端點 檢索您的電子郵件地址和基本個人資料資訊,並建立已驗證的 Session。
Spring Boot 屬性對應
下表概述 Spring Boot OAuth 用戶端屬性與 ClientRegistration 屬性的對應關係。
Spring Boot | ClientRegistration |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ClientRegistration 可以透過探索 OpenID Connect 提供者的 Configuration endpoint 或授權伺服器的 Metadata endpoint 來進行初始設定,方法是指定 spring.security.oauth2.client.provider.[providerId].issuer-uri 屬性。 |
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 自動設定
用於 OAuth 用戶端支援的 Spring Boot 自動設定類別是 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://127.0.0.1/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://127.0.0.1/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://127.0.0.1/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://127.0.0.1/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()
}
}