核心配置

Spring Boot 示例

Spring Boot 为 OAuth 2.0 登录提供了完整的自动配置功能。

本节展示了如何使用Google作为身份验证提供程序配置OAuth 2.0 登录 WebFlux 示例,并涵盖以下主题

初始设置

要使用 Google 的 OAuth 2.0 身份验证系统进行登录,您必须在 Google API 控制台中设置一个项目以获取 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 客户端将从用户信息端点检索您的电子邮件地址和基本个人资料信息,并建立一个经过身份验证的会话。

Spring Boot 属性映射

下表概述了 Spring Boot OAuth 客户端属性与客户端注册属性的映射关系。

Spring Boot 客户端注册

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

可以通过发现 OpenID Connect 提供程序的配置端点或授权服务器的元数据端点,并指定spring.security.oauth2.client.provider.[providerId].issuer-uri属性来初始配置ClientRegistration

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
客户端属性的自动默认值在此处无缝工作,因为registrationIdgoogle)与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()中设置的客户端属性的自动默认值。

配置自定义提供程序属性

某些 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

它执行以下任务

  • 注册一个ReactiveClientRegistrationRepository @Bean,该@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 登录

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://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 的 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()
    }
}