核心設定

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 範本為 {baseUrl}/login/oauth2/code/{registrationId}registrationIdClientRegistration 的唯一識別碼。在我們的範例中,registrationIdgoogle

如果 OAuth 用戶端在代理伺服器後方執行,建議查看 代理伺服器設定,以確保應用程式已正確設定。另請參閱 URI 範本變數 中針對 redirect-uri 支援的變數。

設定 application.yml

現在您已擁有與 Google 建立的新 OAuth 用戶端,您需要設定應用程式以使用該 OAuth 用戶端進行身份驗證流程。若要執行此操作

  1. 前往 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。
  2. client-idclient-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

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

ClientRegistration 可以透過探索 OpenID Connect 提供者的 Configuration endpoint 或授權伺服器的 Metadata endpoint 來進行初始設定,方法是指定 spring.security.oauth2.client.provider.[providerId].issuer-uri 屬性。

CommonOAuth2Provider

CommonOAuth2Provider 為許多知名的提供者預先定義了一組預設的用戶端屬性:Google、GitHub、Facebook 和 Okta。

例如,提供者的 authorization-uritoken-uriuser-info-uri 通常不會變更。因此,提供預設值以便減少所需的設定是有意義的。

如先前所示,當我們 設定 Google 用戶端 時,只需要 client-idclient-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 登入

OAuth2 登入設定
  • 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 @BeanSecurityWebFilterChain @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),請套用以下設定

OAuth2 登入設定
  • 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()
    }
}