Authorization Grant Support

Spring Security 提供了对以下授权赠送类型的支持:

  • 授权代码

  • 密码

  • 客户凭据

  • 客户端断言

  • JWT 持有者

对于每种授权类型,都有一个专门的 OAuth2AuthorizedClientProvider 实现,负责处理授权过程的特定方面。

除了提供对授权赠送的支持之外,Spring Security 还提供了对以下内容的支持:

  • 刷新访问令牌

  • 自定义授权请求和响应

  • 存储授权请求和响应

  • 使用配置的 RestTemplate 配置授权代码令牌响应处理程序

此支持使开发人员能够轻松地将 OAuth 2.0 授权流程集成到他们的 Spring Boot 应用程序中。

本部分介绍 Spring Security 对授权赠送的支持。

Authorization Code

有关 Authorization Code 授权的更多详情,请参阅 OAuth 2.0 授权框架。

Obtaining Authorization

有关授权码授权,请参阅 Authorization Request/Response 协议流程。

Initiating the Authorization Request

OAuth2AuthorizationRequestRedirectFilter 使用 OAuth2AuthorizationRequestResolver 来解析 OAuth2AuthorizationRequest 并通过将最终用户的用户代理重定向到授权服务器的授权端点,从而启动授权码授权流。

OAuth2AuthorizationRequestResolver 的主要作用是从提供的 Web 请求中解析 OAuth2AuthorizationRequest。默认实现 DefaultOAuth2AuthorizationRequestResolver 匹配(默认)路径 /oauth2/authorization/{registrationId},提取 registrationId,并使用它为关联的 ClientRegistration 构建 OAuth2AuthorizationRequest

考虑 OAuth 2.0 客户端注册的以下 Spring Boot 2.x 属性:

spring:
  security:
    oauth2:
      client:
        registration:
          okta:
            client-id: okta-client-id
            client-secret: okta-client-secret
            authorization-grant-type: authorization_code
            redirect-uri: "{baseUrl}/authorized/okta"
            scope: read, write
        provider:
          okta:
            authorization-uri: https://dev-1234.oktapreview.com/oauth2/v1/authorize
            token-uri: https://dev-1234.oktapreview.com/oauth2/v1/token

鉴于前述属性,带有基本路径 /oauth2/authorization/okta 的请求将由 OAuth2AuthorizationRequestRedirectFilter 发起授权请求重定向并最终启动授权码授权流。

AuthorizationCodeOAuth2AuthorizedClientProvider 是授权码授权的 OAuth2AuthorizedClientProvider 的实现,它还由 OAuth2AuthorizationRequestRedirectFilter 发起授权请求重定向。

如果 OAuth 2.0 客户端是 Public Client,请按如下方式配置 OAuth 2.0 客户端注册:

spring:
  security:
    oauth2:
      client:
        registration:
          okta:
            client-id: okta-client-id
            client-authentication-method: none
            authorization-grant-type: authorization_code
            redirect-uri: "{baseUrl}/authorized/okta"
            ...

公有客户端得到 Proof Key for Code Exchange (PKCE) 的支持。如果客户端在不受信任的环境(例如原生应用程序或基于网络浏览器的应用程序)中运行,因此无法维持其凭据的机密性,则在满足以下条件时会自动使用 PKCE:

  1. client-secret 被省略(或为空)

  2. client-authentication-method 设置为 none (ClientAuthenticationMethod.NONE)

如果 OAuth 2.0 提供方支持 Confidential Clients 的 PKCE,您可以(可选)使用 DefaultOAuth2AuthorizationRequestResolver.setAuthorizationRequestCustomizer(OAuth2AuthorizationRequestCustomizers.withPkce()) 来对其进行配置。

DefaultOAuth2AuthorizationRequestResolver 还使用 UriComponentsBuilderredirect-uri 支持 URI 模板变量。

以下配置使用所有受支持的 URI 模板变量:

spring:
  security:
    oauth2:
      client:
        registration:
          okta:
            ...
            redirect-uri: "{baseScheme}://{baseHost}{basePort}{basePath}/authorized/{registrationId}"
            ...

{baseUrl} 解析为 {baseScheme}://{baseHost}{basePort}{basePath}

使用 URI 模板变量配置 redirect-uri 在 OAuth 2.0 客户端在 Proxy Server 后面运行时特别有用。这样做可确保在扩展 redirect-uri 时使用 X-Forwarded-* 头。

Customizing the Authorization Request

OAuth2AuthorizationRequestResolver 可以实现的主要用例之一是能够使用 OAuth 2.0 授权框架中定义的标准参数之外的附加参数自定义授权请求。

例如,OpenID Connect 为 Authorization Code Flow 定义额外的 OAuth 2.0 请求参数,该请求参数从 OAuth 2.0 Authorization Framework 中定义的标准参数扩展而来。这些扩展参数之一是 prompt 参数。

prompt 参数是可选的。空格分隔、区分大小写的 ASCII 字符串值列表,用于指定授权服务器是否提示最终用户进行重新身份验证和同意。定义的值为:noneloginconsentselect_account

以下示例显示如何使用 Consumer<OAuth2AuthorizationRequest.Builder> 配置 DefaultOAuth2AuthorizationRequestResolver,该 Consumer<OAuth2AuthorizationRequest.Builder> 通过包括请求参数 prompt=consent 来定制 oauth2Login() 的授权请求。

  • Java

  • Kotlin

@Configuration
@EnableWebSecurity
public class OAuth2LoginSecurityConfig {

	@Autowired
	private ClientRegistrationRepository clientRegistrationRepository;

	@Bean
	public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
		http
			.authorizeHttpRequests(authorize -> authorize
				.anyRequest().authenticated()
			)
			.oauth2Login(oauth2 -> oauth2
				.authorizationEndpoint(authorization -> authorization
					.authorizationRequestResolver(
						authorizationRequestResolver(this.clientRegistrationRepository)
					)
				)
			);
		return http.build();
	}

	private OAuth2AuthorizationRequestResolver authorizationRequestResolver(
			ClientRegistrationRepository clientRegistrationRepository) {

		DefaultOAuth2AuthorizationRequestResolver authorizationRequestResolver =
				new DefaultOAuth2AuthorizationRequestResolver(
						clientRegistrationRepository, "/oauth2/authorization");
		authorizationRequestResolver.setAuthorizationRequestCustomizer(
				authorizationRequestCustomizer());

		return  authorizationRequestResolver;
	}

	private Consumer<OAuth2AuthorizationRequest.Builder> authorizationRequestCustomizer() {
		return customizer -> customizer
					.additionalParameters(params -> params.put("prompt", "consent"));
	}
}
@Configuration
@EnableWebSecurity
class SecurityConfig {

    @Autowired
    private lateinit var customClientRegistrationRepository: ClientRegistrationRepository

    @Bean
    open fun filterChain(http: HttpSecurity): SecurityFilterChain {
        http {
            authorizeRequests {
                authorize(anyRequest, authenticated)
            }
            oauth2Login {
                authorizationEndpoint {
                    authorizationRequestResolver = authorizationRequestResolver(customClientRegistrationRepository)
                }
            }
        }
        return http.build()
    }

    private fun authorizationRequestResolver(
            clientRegistrationRepository: ClientRegistrationRepository?): OAuth2AuthorizationRequestResolver? {
        val authorizationRequestResolver = DefaultOAuth2AuthorizationRequestResolver(
                clientRegistrationRepository, "/oauth2/authorization")
        authorizationRequestResolver.setAuthorizationRequestCustomizer(
                authorizationRequestCustomizer())
        return authorizationRequestResolver
    }

    private fun authorizationRequestCustomizer(): Consumer<OAuth2AuthorizationRequest.Builder> {
        return Consumer { customizer ->
            customizer
                    .additionalParameters { params -> params["prompt"] = "consent" }
        }
    }
}

对于附加请求参数始终对特定提供程序相同时的简单用例,您可以在 authorization-uri 属性中直接添加它。

例如,如果对提供程序 okta,请求参数 prompt 的值始终为 consent,则可以按如下方式进行配置:

spring:
  security:
    oauth2:
      client:
        provider:
          okta:
            authorization-uri: https://dev-1234.oktapreview.com/oauth2/v1/authorize?prompt=consent

前面的示例显示了在标准参数之上添加自定义参数的常见用例。或者,如果您的要求更高级,则可以通过覆盖 OAuth2AuthorizationRequest.authorizationRequestUri 属性来完全控制构建授权请求 URI。

OAuth2AuthorizationRequest.Builder.build() 构建了 OAuth2AuthorizationRequest.authorizationRequestUri,它表示使用 application/x-www-form-urlencoded 格式的所有查询参数的授权请求 URI。

以下示例显示了前面示例中 authorizationRequestCustomizer() 的一个变体,相反,它覆盖了 OAuth2AuthorizationRequest.authorizationRequestUri 属性:

  • Java

  • Kotlin

private Consumer<OAuth2AuthorizationRequest.Builder> authorizationRequestCustomizer() {
	return customizer -> customizer
				.authorizationRequestUri(uriBuilder -> uriBuilder
					.queryParam("prompt", "consent").build());
}
private fun authorizationRequestCustomizer(): Consumer<OAuth2AuthorizationRequest.Builder> {
    return Consumer { customizer: OAuth2AuthorizationRequest.Builder ->
        customizer
                .authorizationRequestUri { uriBuilder: UriBuilder ->
                    uriBuilder
                            .queryParam("prompt", "consent").build()
                }
    }
}

Storing the Authorization Request

AuthorizationRequestRepository 负责从发起授权请求的时间到收到授权响应(回调)的时间对 OAuth2AuthorizationRequest 进行持久性处理。

OAuth2AuthorizationRequest 用于关联和验证授权响应。

AuthorizationRequestRepository 的默认实现为 HttpSessionOAuth2AuthorizationRequestRepository,该实现将 OAuth2AuthorizationRequest 存储在 HttpSession 中。

如果您具有 AuthorizationRequestRepository 的自定义实现,则可以按如下方式进行配置:

AuthorizationRequestRepository Configuration
  • Java

  • Kotlin

  • Xml

@Configuration
@EnableWebSecurity
public class OAuth2ClientSecurityConfig {

	@Bean
	public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
		http
			.oauth2Client(oauth2 -> oauth2
				.authorizationCodeGrant(codeGrant -> codeGrant
					.authorizationRequestRepository(this.authorizationRequestRepository())
					...
				)
            .oauth2Login(oauth2 -> oauth2
                .authorizationEndpoint(endpoint -> endpoint
                    .authorizationRequestRepository(this.authorizationRequestRepository())
                    ...
                )
            ).build();
	}

    @Bean
    public AuthorizationRequestRepository<OAuth2AuthorizationRequest> authorizationRequestRepository() {
        return new CustomOAuth2AuthorizationRequestRepository();
    }
}
@Configuration
@EnableWebSecurity
class OAuth2ClientSecurityConfig {

    @Bean
    open fun filterChain(http: HttpSecurity): SecurityFilterChain {
        http {
            oauth2Client {
                authorizationCodeGrant {
                    authorizationRequestRepository = authorizationRequestRepository()
                }
            }
        }
        return http.build()
    }
}
<http>
	<oauth2-client>
		<authorization-code-grant authorization-request-repository-ref="authorizationRequestRepository"/>
	</oauth2-client>
</http>

Requesting an Access Token

有关授权码授权,请参阅 Access Token Request/Response 协议流程。

授权码授权的 OAuth2AccessTokenResponseClient 的默认实现为 DefaultAuthorizationCodeTokenResponseClient,它使用 RestOperations 实例在授权服务器令牌端点处将授权码交换为访问令牌。

DefaultAuthorizationCodeTokenResponseClient 十分灵活,因为它允许你自定义令牌请求预处理和/或令牌响应后处理。

Customizing the Access Token Request

如果您需要自定义令牌请求的预处理,您可以为 DefaultAuthorizationCodeTokenResponseClient.setRequestEntityConverter() 提供自定义 Converter<OAuth2AuthorizationCodeGrantRequest, RequestEntity<?>>。默认实施(OAuth2AuthorizationCodeGrantRequestEntityConverter)构建标准 OAuth 2.0 Access Token RequestRequestEntity 表示形式。但是,提供一个自定义 Converter 将让您扩展标准令牌请求并添加自定义参数。

要仅自定义请求的参数,可以使用自定义 Converter<OAuth2AuthorizationCodeGrantRequest, MultiValueMap<String, String>>OAuth2AuthorizationCodeGrantRequestEntityConverter.setParametersConverter() 提供,以完全覆盖随请求发送的参数。这通常比直接构建 RequestEntity 更简单。

如果你更愿意只添加其他参数,可以使用自定义 Converter<OAuth2AuthorizationCodeGrantRequest, MultiValueMap<String, String>>OAuth2AuthorizationCodeGrantRequestEntityConverter.addParametersConverter() 提供以构建聚合 Converter

自定义 Converter 必须返回 OAuth 2.0 访问令牌请求的有效 RequestEntity 表示,该访问令牌请求由目标 OAuth 2.0 提供商理解。

Customizing the Access Token Response

另一方面,如果你需要自定义令牌响应后处理,则需要使用自定义配置的 RestOperationsDefaultAuthorizationCodeTokenResponseClient.setRestOperations() 提供。默认 RestOperations 配置如下:

  • Java

  • Kotlin

RestTemplate restTemplate = new RestTemplate(Arrays.asList(
		new FormHttpMessageConverter(),
		new OAuth2AccessTokenResponseHttpMessageConverter()));

restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
val restTemplate = RestTemplate(listOf(
        FormHttpMessageConverter(),
        OAuth2AccessTokenResponseHttpMessageConverter()))

restTemplate.errorHandler = OAuth2ErrorResponseErrorHandler()

需要 Spring MVC FormHttpMessageConverter ,因为在发送 OAuth 2.0 访问令牌请求时会使用它。

OAuth2AccessTokenResponseHttpMessageConverter 是 OAuth 2.0 访问令牌响应的 HttpMessageConverter 。可以使用自定义 Converter<Map<String, Object>, OAuth2AccessTokenResponse>OAuth2AccessTokenResponseHttpMessageConverter.setAccessTokenResponseConverter() 提供,用于将 OAuth 2.0 访问令牌响应参数转换为 OAuth2AccessTokenResponse

OAuth2ErrorResponseErrorHandlerResponseErrorHandler,它可以处理 OAuth 2.0 错误,如 400 Bad Request。它使用 OAuth2ErrorHttpMessageConverter 将 OAuth 2.0 错误参数转换为 OAuth2Error

无论你自定义 DefaultAuthorizationCodeTokenResponseClient 还是提供你自己的 OAuth2AccessTokenResponseClient 实施,都需要按如下方式进行配置:

Access Token Response Configuration
  • Java

  • Kotlin

  • Xml

@Configuration
@EnableWebSecurity
public class OAuth2ClientSecurityConfig {

	@Bean
	public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
		http
			.oauth2Client(oauth2 -> oauth2
				.authorizationCodeGrant(codeGrant -> codeGrant
					.accessTokenResponseClient(this.accessTokenResponseClient())
					...
				)
			);
		return http.build();
	}
}
@Configuration
@EnableWebSecurity
class OAuth2ClientSecurityConfig {

    @Bean
    open fun filterChain(http: HttpSecurity): SecurityFilterChain {
        http {
            oauth2Client {
                authorizationCodeGrant {
                    accessTokenResponseClient = accessTokenResponseClient()
                }
            }
        }
        return http.build()
    }
}
<http>
	<oauth2-client>
		<authorization-code-grant access-token-response-client-ref="accessTokenResponseClient"/>
	</oauth2-client>
</http>

Refresh Token

有关 Refresh Token 的更多详情,请参阅 OAuth 2.0 授权框架。

Refreshing an Access Token

有关刷新令牌授权,请参阅 Access Token Request/Response 协议流程。

OAuth2AccessTokenResponseClient 中刷新令牌授权的默认实施为 DefaultRefreshTokenTokenResponseClient,它在授权服务器令牌端点刷新访问令牌时使用 RestOperations

DefaultRefreshTokenTokenResponseClient 十分灵活,因为它允许自定义令牌请求预处理或令牌响应后处理。

Customizing the Access Token Request

如果您需要为令牌请求自定义预处理,则可以向 DefaultRefreshTokenTokenResponseClient.setRequestEntityConverter() 提供自定义 Converter<OAuth2RefreshTokenGrantRequest, RequestEntity<?>> 。默认实现 (OAuth2RefreshTokenGrantRequestEntityConverter) 会构建标准 OAuth 2.0 Access Token RequestRequestEntity 表示。但是,提供自定义 Converter 可以让您扩展标准令牌请求并添加自定义参数。

要仅自定义请求的参数,可以使用自定义 Converter<OAuth2RefreshTokenGrantRequest, MultiValueMap<String, String>>OAuth2RefreshTokenGrantRequestEntityConverter.setParametersConverter() 提供,以完全覆盖随请求发送的参数。这通常比直接构建 RequestEntity 更简单。

如果你更愿意只添加其他参数,可以使用自定义 Converter<OAuth2RefreshTokenGrantRequest, MultiValueMap<String, String>>OAuth2RefreshTokenGrantRequestEntityConverter.addParametersConverter() 提供以构建聚合 Converter

自定义 Converter 必须返回 OAuth 2.0 访问令牌请求的有效 RequestEntity 表示,该访问令牌请求由目标 OAuth 2.0 提供商理解。

Customizing the Access Token Response

另一方面,如果你需要自定义令牌响应后处理,则需要使用自定义配置的 RestOperationsDefaultRefreshTokenTokenResponseClient.setRestOperations() 提供。默认 RestOperations 配置如下:

  • Java

  • Kotlin

RestTemplate restTemplate = new RestTemplate(Arrays.asList(
		new FormHttpMessageConverter(),
		new OAuth2AccessTokenResponseHttpMessageConverter()));

restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
val restTemplate = RestTemplate(listOf(
        FormHttpMessageConverter(),
        OAuth2AccessTokenResponseHttpMessageConverter()))

restTemplate.errorHandler = OAuth2ErrorResponseErrorHandler()

需要 Spring MVC FormHttpMessageConverter ,因为在发送 OAuth 2.0 访问令牌请求时会使用它。

OAuth2AccessTokenResponseHttpMessageConverter 是 OAuth 2.0 访问令牌响应的 HttpMessageConverter 。可以使用自定义 Converter<Map<String, Object>, OAuth2AccessTokenResponse>OAuth2AccessTokenResponseHttpMessageConverter.setAccessTokenResponseConverter() 提供,用于将 OAuth 2.0 访问令牌响应参数转换为 OAuth2AccessTokenResponse

OAuth2ErrorResponseErrorHandlerResponseErrorHandler,它可以处理 OAuth 2.0 错误,如 400 Bad Request。它使用 OAuth2ErrorHttpMessageConverter 将 OAuth 2.0 错误参数转换为 OAuth2Error

无论你自定义 DefaultRefreshTokenTokenResponseClient 还是提供你自己的 OAuth2AccessTokenResponseClient 实施,都需要按如下方式进行配置:

  • Java

  • Kotlin

// Customize
OAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> refreshTokenTokenResponseClient = ...

OAuth2AuthorizedClientProvider authorizedClientProvider =
		OAuth2AuthorizedClientProviderBuilder.builder()
				.authorizationCode()
				.refreshToken(configurer -> configurer.accessTokenResponseClient(refreshTokenTokenResponseClient))
				.build();

...

authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
// Customize
val refreshTokenTokenResponseClient: OAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> = ...

val authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
        .authorizationCode()
        .refreshToken { it.accessTokenResponseClient(refreshTokenTokenResponseClient) }
        .build()

...

authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)

OAuth2AuthorizedClientProviderBuilder.builder().refreshToken() 会配置一个 RefreshTokenOAuth2AuthorizedClientProvider,它是 OAuth2AuthorizedClientProvider 的一种实现,适用于 Refresh Token 授权。

对于 authorization_codepassword 授权类型,选项为在 Access Token 响应中返回 OAuth2RefreshToken。如果 OAuth2AuthorizedClient.getRefreshToken() 可用,且 OAuth2AuthorizedClient.getAccessToken() 已过期,则 RefreshTokenOAuth2AuthorizedClientProvider 会自动刷新它。

Client Credentials

请参阅 OAuth 2.0 授权框架,进一步了解 Client Credentials 授权。

Requesting an Access Token

请参阅 OAuth 2.0 授权框架以详细了解 Client Credentials 授权。

OAuth2AccessTokenResponseClient 的默认实现是 Client Credentials 授权的 DefaultClientCredentialsTokenResponseClient,它在 Authorization Server 的 Token 终端请求访问令牌时使用 RestOperations

DefaultClientCredentialsTokenResponseClient 是灵活的,因为它允许您自定义令牌请求的预处理或令牌响应的后处理。

Customizing the Access Token Request

如果您需要为令牌请求自定义预处理,则可以向 DefaultClientCredentialsTokenResponseClient.setRequestEntityConverter() 提供自定义 Converter<OAuth2ClientCredentialsGrantRequest, RequestEntity<?>> 。默认实现 (OAuth2ClientCredentialsGrantRequestEntityConverter) 会构建标准 OAuth 2.0 Access Token RequestRequestEntity 表示。但是,提供自定义 Converter 可以让您扩展标准令牌请求并添加自定义参数。

要仅自定义请求的参数,您可以为 OAuth2ClientCredentialsGrantRequestEntityConverter.setParametersConverter() 提供一个自定义的 Converter<OAuth2ClientCredentialsGrantRequest, MultiValueMap<String, String>>,以完全覆盖随请求发送的参数。这通常比直接构建 RequestEntity 更加容易。

如果您只想添加其他的参数,您可以为 OAuth2ClientCredentialsGrantRequestEntityConverter.addParametersConverter() 提供一个自定义的 Converter<OAuth2ClientCredentialsGrantRequest, MultiValueMap<String, String>>,用以构建一个总体的 Converter

自定义 Converter 必须返回 OAuth 2.0 访问令牌请求的有效 RequestEntity 表示,该访问令牌请求由目标 OAuth 2.0 提供商理解。

Customizing the Access Token Response

另一方面,如果您需要自定义令牌响应的后处理,则需要为 DefaultClientCredentialsTokenResponseClient.setRestOperations() 提供一个自定义配置的 RestOperations。默认 RestOperations 配置如下:

  • Java

  • Kotlin

RestTemplate restTemplate = new RestTemplate(Arrays.asList(
		new FormHttpMessageConverter(),
		new OAuth2AccessTokenResponseHttpMessageConverter()));

restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
val restTemplate = RestTemplate(listOf(
        FormHttpMessageConverter(),
        OAuth2AccessTokenResponseHttpMessageConverter()))

restTemplate.errorHandler = OAuth2ErrorResponseErrorHandler()

需要 Spring MVC FormHttpMessageConverter ,因为在发送 OAuth 2.0 访问令牌请求时会使用它。

OAuth2AccessTokenResponseHttpMessageConverter 是 OAuth 2.0 访问令牌响应的 HttpMessageConverter 。可以使用自定义 Converter<Map<String, Object>, OAuth2AccessTokenResponse>OAuth2AccessTokenResponseHttpMessageConverter.setAccessTokenResponseConverter() 提供,用于将 OAuth 2.0 访问令牌响应参数转换为 OAuth2AccessTokenResponse

OAuth2ErrorResponseErrorHandler 是一个 ResponseErrorHandler,它可以处理 OAuth 2.0 错误,如 400 Bad Request。它使用 OAuth2ErrorHttpMessageConverter 将 OAuth 2.0 错误参数转换为 OAuth2Error

无论您是自定义 DefaultClientCredentialsTokenResponseClient 还是提供您自己的 OAuth2AccessTokenResponseClient 实现方法,都需要按照如下所述配置它:

  • Java

  • Kotlin

// Customize
OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> clientCredentialsTokenResponseClient = ...

OAuth2AuthorizedClientProvider authorizedClientProvider =
		OAuth2AuthorizedClientProviderBuilder.builder()
				.clientCredentials(configurer -> configurer.accessTokenResponseClient(clientCredentialsTokenResponseClient))
				.build();

...

authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
// Customize
val clientCredentialsTokenResponseClient: OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> = ...

val authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
        .clientCredentials { it.accessTokenResponseClient(clientCredentialsTokenResponseClient) }
        .build()

...

authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)

OAuth2AuthorizedClientProviderBuilder.builder().clientCredentials() 会配置一个 ClientCredentialsOAuth2AuthorizedClientProvider,它是 OAuth2AuthorizedClientProvider 的一种实现,适用于 Client Credentials 授权。

Using the Access Token

考虑 OAuth 2.0 客户端注册的以下 Spring Boot 2.x 属性:

spring:
  security:
    oauth2:
      client:
        registration:
          okta:
            client-id: okta-client-id
            client-secret: okta-client-secret
            authorization-grant-type: client_credentials
            scope: read, write
        provider:
          okta:
            token-uri: https://dev-1234.oktapreview.com/oauth2/v1/token

进一步考虑以下 OAuth2AuthorizedClientManager @Bean

  • Java

  • Kotlin

@Bean
public OAuth2AuthorizedClientManager authorizedClientManager(
		ClientRegistrationRepository clientRegistrationRepository,
		OAuth2AuthorizedClientRepository authorizedClientRepository) {

	OAuth2AuthorizedClientProvider authorizedClientProvider =
			OAuth2AuthorizedClientProviderBuilder.builder()
					.clientCredentials()
					.build();

	DefaultOAuth2AuthorizedClientManager authorizedClientManager =
			new DefaultOAuth2AuthorizedClientManager(
					clientRegistrationRepository, authorizedClientRepository);
	authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);

	return authorizedClientManager;
}
@Bean
fun authorizedClientManager(
        clientRegistrationRepository: ClientRegistrationRepository,
        authorizedClientRepository: OAuth2AuthorizedClientRepository): OAuth2AuthorizedClientManager {
    val authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
            .clientCredentials()
            .build()
    val authorizedClientManager = DefaultOAuth2AuthorizedClientManager(
            clientRegistrationRepository, authorizedClientRepository)
    authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
    return authorizedClientManager
}

鉴于前面的属性和 bean,您可以按照如下方式获取 OAuth2AccessToken

  • Java

  • Kotlin

@Controller
public class OAuth2ClientController {

	@Autowired
	private OAuth2AuthorizedClientManager authorizedClientManager;

	@GetMapping("/")
	public String index(Authentication authentication,
						HttpServletRequest servletRequest,
						HttpServletResponse servletResponse) {

		OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId("okta")
				.principal(authentication)
				.attributes(attrs -> {
					attrs.put(HttpServletRequest.class.getName(), servletRequest);
					attrs.put(HttpServletResponse.class.getName(), servletResponse);
				})
				.build();
		OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest);

		OAuth2AccessToken accessToken = authorizedClient.getAccessToken();

		...

		return "index";
	}
}
class OAuth2ClientController {

    @Autowired
    private lateinit var authorizedClientManager: OAuth2AuthorizedClientManager

    @GetMapping("/")
    fun index(authentication: Authentication?,
              servletRequest: HttpServletRequest,
              servletResponse: HttpServletResponse): String {
        val authorizeRequest: OAuth2AuthorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId("okta")
                .principal(authentication)
                .attributes(Consumer { attrs: MutableMap<String, Any> ->
                    attrs[HttpServletRequest::class.java.name] = servletRequest
                    attrs[HttpServletResponse::class.java.name] = servletResponse
                })
                .build()
        val authorizedClient = authorizedClientManager.authorize(authorizeRequest)
        val accessToken: OAuth2AccessToken = authorizedClient.accessToken

        ...

        return "index"
    }
}

HttpServletRequestHttpServletResponse 都是可选属性。如果您未指定它们,它们会使用 RequestContextHolder.getRequestAttributes() 默认使用 ServletRequestAttributes

Resource Owner Password Credentials

请参阅 OAuth 2.0 授权框架以详细了解 Resource Owner Password Credentials 授权。

Requesting an Access Token

请参阅 Access Token Request/Response 协议流程以了解资源所有者密码凭据授权。

OAuth2AccessTokenResponseClient 的默认实现是资源所有者密码凭证授权的 DefaultPasswordTokenResponseClient,它在 Authorization Server 的 Token 终端请求访问令牌时使用 RestOperations

DefaultPasswordTokenResponseClient 是灵活的,因为它允许您自定义令牌请求的预处理或令牌响应的后处理。

Customizing the Access Token Request

如果您需要为令牌请求自定义预处理,则可以向 DefaultPasswordTokenResponseClient.setRequestEntityConverter() 提供自定义 Converter<OAuth2PasswordGrantRequest, RequestEntity<?>> 。默认实现 (OAuth2PasswordGrantRequestEntityConverter) 会构建标准 OAuth 2.0 Access Token RequestRequestEntity 表示。但是,提供自定义 Converter 可以让您扩展标准令牌请求并添加自定义参数。

要仅自定义请求的参数,可为 OAuth2PasswordGrantRequestEntityConverter.setParametersConverter() 提供自定义 Converter<OAuth2PasswordGrantRequest, MultiValueMap<String, String>> 以完全覆盖使用该请求发送的参数。这通常比直接构建 RequestEntity 更加简单。

如果你只添加附加参数,可为 OAuth2PasswordGrantRequestEntityConverter.addParametersConverter() 提供自定义 Converter<OAuth2PasswordGrantRequest, MultiValueMap<String, String>>,它会构建一个聚合 Converter

自定义 Converter 必须返回 OAuth 2.0 访问令牌请求的有效 RequestEntity 表示,该访问令牌请求由目标 OAuth 2.0 提供商理解。

Customizing the Access Token Response

另一方面,如果你需要自定义令牌响应的后处理,需要使用自定义配置的 RestOperationsDefaultPasswordTokenResponseClient.setRestOperations() 提供。默认 RestOperations 的配置如下:

  • Java

  • Kotlin

RestTemplate restTemplate = new RestTemplate(Arrays.asList(
		new FormHttpMessageConverter(),
		new OAuth2AccessTokenResponseHttpMessageConverter()));

restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
val restTemplate = RestTemplate(listOf(
        FormHttpMessageConverter(),
        OAuth2AccessTokenResponseHttpMessageConverter()))

restTemplate.errorHandler = OAuth2ErrorResponseErrorHandler()

需要 Spring MVC FormHttpMessageConverter ,因为在发送 OAuth 2.0 访问令牌请求时会使用它。

OAuth2AccessTokenResponseHttpMessageConverter 是 OAuth 2.0 访问令牌响应的 HttpMessageConverter。可为 OAuth2AccessTokenResponseHttpMessageConverter.setTokenResponseConverter() 提供自定义 Converter<Map<String, String>, OAuth2AccessTokenResponse>,它用于将 OAuth 2.0 访问令牌响应参数转换为 OAuth2AccessTokenResponse

OAuth2ErrorResponseErrorHandler 是一个 ResponseErrorHandler,它可以处理 OAuth 2.0 错误,如 400 Bad Request。它使用 OAuth2ErrorHttpMessageConverter 将 OAuth 2.0 错误参数转换为 OAuth2Error

无论你自定义 DefaultPasswordTokenResponseClient 或提供 OAuth2AccessTokenResponseClient 自己的实现,都需要按照以下方式进行配置:

  • Java

  • Kotlin

// Customize
OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> passwordTokenResponseClient = ...

OAuth2AuthorizedClientProvider authorizedClientProvider =
		OAuth2AuthorizedClientProviderBuilder.builder()
				.password(configurer -> configurer.accessTokenResponseClient(passwordTokenResponseClient))
				.refreshToken()
				.build();

...

authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
val passwordTokenResponseClient: OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> = ...

val authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
        .password { it.accessTokenResponseClient(passwordTokenResponseClient) }
        .refreshToken()
        .build()

...

authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)

OAuth2AuthorizedClientProviderBuilder.builder().password() 配置 PasswordOAuth2AuthorizedClientProvider,它是资源所有者密码凭据授权的 OAuth2AuthorizedClientProvider 实现。

Using the Access Token

考虑 OAuth 2.0 客户端注册的以下 Spring Boot 2.x 属性:

spring:
  security:
    oauth2:
      client:
        registration:
          okta:
            client-id: okta-client-id
            client-secret: okta-client-secret
            authorization-grant-type: password
            scope: read, write
        provider:
          okta:
            token-uri: https://dev-1234.oktapreview.com/oauth2/v1/token

此外,还需要考虑 OAuth2AuthorizedClientManager @Bean

  • Java

  • Kotlin

@Bean
public OAuth2AuthorizedClientManager authorizedClientManager(
		ClientRegistrationRepository clientRegistrationRepository,
		OAuth2AuthorizedClientRepository authorizedClientRepository) {

	OAuth2AuthorizedClientProvider authorizedClientProvider =
			OAuth2AuthorizedClientProviderBuilder.builder()
					.password()
					.refreshToken()
					.build();

	DefaultOAuth2AuthorizedClientManager authorizedClientManager =
			new DefaultOAuth2AuthorizedClientManager(
					clientRegistrationRepository, authorizedClientRepository);
	authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);

	// Assuming the `username` and `password` are supplied as `HttpServletRequest` parameters,
	// map the `HttpServletRequest` parameters to `OAuth2AuthorizationContext.getAttributes()`
	authorizedClientManager.setContextAttributesMapper(contextAttributesMapper());

	return authorizedClientManager;
}

private Function<OAuth2AuthorizeRequest, Map<String, Object>> contextAttributesMapper() {
	return authorizeRequest -> {
		Map<String, Object> contextAttributes = Collections.emptyMap();
		HttpServletRequest servletRequest = authorizeRequest.getAttribute(HttpServletRequest.class.getName());
		String username = servletRequest.getParameter(OAuth2ParameterNames.USERNAME);
		String password = servletRequest.getParameter(OAuth2ParameterNames.PASSWORD);
		if (StringUtils.hasText(username) && StringUtils.hasText(password)) {
			contextAttributes = new HashMap<>();

			// `PasswordOAuth2AuthorizedClientProvider` requires both attributes
			contextAttributes.put(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, username);
			contextAttributes.put(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, password);
		}
		return contextAttributes;
	};
}
@Bean
fun authorizedClientManager(
        clientRegistrationRepository: ClientRegistrationRepository,
        authorizedClientRepository: OAuth2AuthorizedClientRepository): OAuth2AuthorizedClientManager {
    val authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
            .password()
            .refreshToken()
            .build()
    val authorizedClientManager = DefaultOAuth2AuthorizedClientManager(
            clientRegistrationRepository, authorizedClientRepository)
    authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)

    // Assuming the `username` and `password` are supplied as `HttpServletRequest` parameters,
    // map the `HttpServletRequest` parameters to `OAuth2AuthorizationContext.getAttributes()`
    authorizedClientManager.setContextAttributesMapper(contextAttributesMapper())
    return authorizedClientManager
}

private fun contextAttributesMapper(): Function<OAuth2AuthorizeRequest, MutableMap<String, Any>> {
    return Function { authorizeRequest ->
        var contextAttributes: MutableMap<String, Any> = mutableMapOf()
        val servletRequest: HttpServletRequest = authorizeRequest.getAttribute(HttpServletRequest::class.java.name)
        val username = servletRequest.getParameter(OAuth2ParameterNames.USERNAME)
        val password = servletRequest.getParameter(OAuth2ParameterNames.PASSWORD)
        if (StringUtils.hasText(username) && StringUtils.hasText(password)) {
            contextAttributes = hashMapOf()

            // `PasswordOAuth2AuthorizedClientProvider` requires both attributes
            contextAttributes[OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME] = username
            contextAttributes[OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME] = password
        }
        contextAttributes
    }
}

鉴于前面的属性和 bean,您可以按照如下方式获取 OAuth2AccessToken

  • Java

  • Kotlin

@Controller
public class OAuth2ClientController {

	@Autowired
	private OAuth2AuthorizedClientManager authorizedClientManager;

	@GetMapping("/")
	public String index(Authentication authentication,
						HttpServletRequest servletRequest,
						HttpServletResponse servletResponse) {

		OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId("okta")
				.principal(authentication)
				.attributes(attrs -> {
					attrs.put(HttpServletRequest.class.getName(), servletRequest);
					attrs.put(HttpServletResponse.class.getName(), servletResponse);
				})
				.build();
		OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest);

		OAuth2AccessToken accessToken = authorizedClient.getAccessToken();

		...

		return "index";
	}
}
@Controller
class OAuth2ClientController {
    @Autowired
    private lateinit var authorizedClientManager: OAuth2AuthorizedClientManager

    @GetMapping("/")
    fun index(authentication: Authentication?,
              servletRequest: HttpServletRequest,
              servletResponse: HttpServletResponse): String {
        val authorizeRequest: OAuth2AuthorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId("okta")
                .principal(authentication)
                .attributes(Consumer {
                    it[HttpServletRequest::class.java.name] = servletRequest
                    it[HttpServletResponse::class.java.name] = servletResponse
                })
                .build()
        val authorizedClient = authorizedClientManager.authorize(authorizeRequest)
        val accessToken: OAuth2AccessToken = authorizedClient.accessToken

        ...

        return "index"
    }
}

HttpServletRequestHttpServletResponse 均为可选属性。如果未提供,它们将使用 RequestContextHolder.getRequestAttributes() 默认 ServletRequestAttributes

JWT Bearer

请参阅 JSON Web Token (JWT) OAuth 2.0 客户端认证和认证授予简介以详细了解 JWT Bearer 授权。

Requesting an Access Token

请参阅 Access Token Request/Response 协议流程以了解 JWT 持有者授权。

OAuth2AccessTokenResponseClient 用于 JWT 持有者的默认实现为 DefaultJwtBearerTokenResponseClient,它在授权服务器的令牌端点请求访问令牌时使用 RestOperations

DefaultJwtBearerTokenResponseClient 非常灵活,因为它允许你自定义令牌请求的预处理和/或令牌响应的后处理。

Customizing the Access Token Request

如果您需要为令牌请求自定义预处理,则可以向 DefaultJwtBearerTokenResponseClient.setRequestEntityConverter() 提供自定义 Converter<JwtBearerGrantRequest, RequestEntity<?>> 。默认实现 JwtBearerGrantRequestEntityConverter 会构建 OAuth 2.0 Access Token RequestRequestEntity 表示。但是,提供自定义 Converter 可以让您扩展令牌请求并添加自定义参数。

要仅自定义请求的参数,可为 JwtBearerGrantRequestEntityConverter.setParametersConverter() 提供自定义 Converter<JwtBearerGrantRequest, MultiValueMap<String, String>> 以完全覆盖使用该请求发送的参数。这通常比直接构建 RequestEntity 更加简单。

如果您愿意仅添加其他参数,可以为 JwtBearerGrantRequestEntityConverter.addParametersConverter() 提供自定义 Converter<JwtBearerGrantRequest, MultiValueMap<String, String>>,由其构建一个聚合 Converter

Customizing the Access Token Response

另一方面,如果你需要自定义令牌响应的后处理,将需要使用自定义配置的 RestOperationsDefaultJwtBearerTokenResponseClient.setRestOperations() 提供。默认 RestOperations 的配置如下:

  • Java

  • Kotlin

RestTemplate restTemplate = new RestTemplate(Arrays.asList(
		new FormHttpMessageConverter(),
		new OAuth2AccessTokenResponseHttpMessageConverter()));

restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
val restTemplate = RestTemplate(listOf(
        FormHttpMessageConverter(),
        OAuth2AccessTokenResponseHttpMessageConverter()))

restTemplate.errorHandler = OAuth2ErrorResponseErrorHandler()

需要 Spring MVC FormHttpMessageConverter,因为在发送 OAuth 2.0 访问令牌请求时会使用它。

OAuth2AccessTokenResponseHttpMessageConverter 是 OAuth 2.0 访问令牌响应的 HttpMessageConverter 。可以使用自定义 Converter<Map<String, Object>, OAuth2AccessTokenResponse>OAuth2AccessTokenResponseHttpMessageConverter.setAccessTokenResponseConverter() 提供,用于将 OAuth 2.0 访问令牌响应参数转换为 OAuth2AccessTokenResponse

OAuth2ErrorResponseErrorHandler 是一个 ResponseErrorHandler,它可以处理 OAuth 2.0 错误,例如 400 Bad Request。它使用 OAuth2ErrorHttpMessageConverter 将 OAuth 2.0 错误参数转换为 OAuth2Error

无论你自定义 DefaultJwtBearerTokenResponseClient 或提供 OAuth2AccessTokenResponseClient 自己的实现,都需要按照以下示例所示对其进行配置:

  • Java

  • Kotlin

// Customize
OAuth2AccessTokenResponseClient<JwtBearerGrantRequest> jwtBearerTokenResponseClient = ...

JwtBearerOAuth2AuthorizedClientProvider jwtBearerAuthorizedClientProvider = new JwtBearerOAuth2AuthorizedClientProvider();
jwtBearerAuthorizedClientProvider.setAccessTokenResponseClient(jwtBearerTokenResponseClient);

OAuth2AuthorizedClientProvider authorizedClientProvider =
		OAuth2AuthorizedClientProviderBuilder.builder()
				.provider(jwtBearerAuthorizedClientProvider)
				.build();

...

authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
// Customize
val jwtBearerTokenResponseClient: OAuth2AccessTokenResponseClient<JwtBearerGrantRequest> = ...

val jwtBearerAuthorizedClientProvider = JwtBearerOAuth2AuthorizedClientProvider()
jwtBearerAuthorizedClientProvider.setAccessTokenResponseClient(jwtBearerTokenResponseClient);

val authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
        .provider(jwtBearerAuthorizedClientProvider)
        .build()

...

authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)

Using the Access Token

给定 OAuth 2.0 客户端注册的以下 Spring Boot 2.x 属性:

spring:
  security:
    oauth2:
      client:
        registration:
          okta:
            client-id: okta-client-id
            client-secret: okta-client-secret
            authorization-grant-type: urn:ietf:params:oauth:grant-type:jwt-bearer
            scope: read
        provider:
          okta:
            token-uri: https://dev-1234.oktapreview.com/oauth2/v1/token

…​和 OAuth2AuthorizedClientManager @Bean

  • Java

  • Kotlin

@Bean
public OAuth2AuthorizedClientManager authorizedClientManager(
		ClientRegistrationRepository clientRegistrationRepository,
		OAuth2AuthorizedClientRepository authorizedClientRepository) {

	JwtBearerOAuth2AuthorizedClientProvider jwtBearerAuthorizedClientProvider =
			new JwtBearerOAuth2AuthorizedClientProvider();

	OAuth2AuthorizedClientProvider authorizedClientProvider =
			OAuth2AuthorizedClientProviderBuilder.builder()
					.provider(jwtBearerAuthorizedClientProvider)
					.build();

	DefaultOAuth2AuthorizedClientManager authorizedClientManager =
			new DefaultOAuth2AuthorizedClientManager(
					clientRegistrationRepository, authorizedClientRepository);
	authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);

	return authorizedClientManager;
}
@Bean
fun authorizedClientManager(
        clientRegistrationRepository: ClientRegistrationRepository,
        authorizedClientRepository: OAuth2AuthorizedClientRepository): OAuth2AuthorizedClientManager {
    val jwtBearerAuthorizedClientProvider = JwtBearerOAuth2AuthorizedClientProvider()
    val authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
            .provider(jwtBearerAuthorizedClientProvider)
            .build()
    val authorizedClientManager = DefaultOAuth2AuthorizedClientManager(
            clientRegistrationRepository, authorizedClientRepository)
    authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
    return authorizedClientManager
}

可按如下方式获取 OAuth2AccessToken

  • Java

  • Kotlin

@RestController
public class OAuth2ResourceServerController {

	@Autowired
	private OAuth2AuthorizedClientManager authorizedClientManager;

	@GetMapping("/resource")
	public String resource(JwtAuthenticationToken jwtAuthentication) {
		OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId("okta")
				.principal(jwtAuthentication)
				.build();
		OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest);
		OAuth2AccessToken accessToken = authorizedClient.getAccessToken();

		...

	}
}
class OAuth2ResourceServerController {

    @Autowired
    private lateinit var authorizedClientManager: OAuth2AuthorizedClientManager

    @GetMapping("/resource")
    fun resource(jwtAuthentication: JwtAuthenticationToken?): String {
        val authorizeRequest: OAuth2AuthorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId("okta")
                .principal(jwtAuthentication)
                .build()
        val authorizedClient = authorizedClientManager.authorize(authorizeRequest)
        val accessToken: OAuth2AccessToken = authorizedClient.accessToken

        ...

    }
}

JwtBearerOAuth2AuthorizedClientProvider 默认通过 OAuth2AuthorizationContext.getPrincipal().getPrincipal() 解析 Jwt 断言,因此在前面的示例中使用了 JwtAuthenticationToken

如果您需要从不同来源解析 Jwt 断言,可以为 JwtBearerOAuth2AuthorizedClientProvider.setJwtAssertionResolver() 提供自定义 Function<OAuth2AuthorizationContext, Jwt>