授权许可的支持

本站(springdoc.cn)中的内容来源于 spring.io ,原始版权归属于 spring.io。由 springdoc.cn 进行翻译,整理。可供个人学习、研究,未经许可,不得进行任何转载、商用或与之相关的行为。 商标声明:Spring 是 Pivotal Software, Inc. 在美国以及其他国家的商标。

本节介绍Spring Security对授权许可的支持。

授权代码

关于 授权代码 授予的进一步细节,请参见OAuth 2.0授权框架。

获得授权

参见授权代码授予的 授权请求/响应协议流程。

发起授权请求

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

OAuth2AuthorizationRequestResolver 的主要作用是从提供的web请求中解析出一个 OAuth2AuthorizationRequest 。默认实现 DefaultOAuth2AuthorizationRequestResolver 在(默认)路径 /oauth2/authorization/{registrationId} 上进行匹配,提取 registrationId,并使用它来建立相关 ClientRegistrationOAuth2AuthorizationRequest

考虑以下Spring Boot 2.x属性,用于OAuth 2.0客户端注册。

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 的授权请求重定向,并最终启动授权代码授予流程。

AuthorizationCodeOAuth2AuthorizedClientProviderOAuth2AuthorizedClientProvider 的一个实现,用于授权码的授予,它也启动了 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"
            ...

公共客户端是通过使用 代码交换的证明密钥(PKCE)来支持的。如果客户端运行在一个不受信任的环境中(如本地应用程序或基于Web浏览器的应用程序),因此没有能力维护其证书的保密性,当以下条件为真(true)时,会自动使用PKCE。

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

  2. client-authentication-method 被设置为 noneClientAuthenticationMethod.NONE)。

如果OAuth 2.0提供商支持 Confidential Clients 的PKCE,你可以(选择性地)使用 DefaultOAuth2AuthorizationRequestResolver.setAuthorizationRequestCustomizer(OAuth2AuthorizationRequestCustomizers.withPkce()) 进行配置。

DefaultOAuth2AuthorizationRequestResolver 还通过使用 UriComponentsBuilder 支持URI模板变量用于 redirect-uri

下面的配置使用了所有支持的 URI 模板变量。

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

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

当OAuth 2.0客户端在 代理服务器 后面运行时,用 URI 模板变量配置 redirect-uri 特别有用。这样做可以确保在扩展 redirect-uri 时使用 X-Forwarded-* 头信息。

自定义授权请求

OAuth2AuthorizationRequestResolver 可以实现的一个主要用例是,在OAuth 2.0授权框架中定义的标准参数之上,用额外的参数来定制授权请求的能力。

例如,OpenID Connect为 Authorization Code Flow 定义了额外的OAuth 2.0请求参数,这些参数是从 OAuth 2.0授权框架 中定义的标准参数延伸出来的。其中一个扩展参数是 prompt 参数。

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

下面的例子显示了如何用一个 Consumer<OAuth2AuthorizationRequest.Builder> 来配置 DefaultOAuth2AuthorizationRequestResolver,该 consumer 通过包括请求参数 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 属性中添加它。

例如,如果请求参数 prompt 对提供者 okta 来说总是同意的,你可以这样配置它。

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,它表示授权请求URI,包括使用 application/x-www-form-urlencoded 格式的所有查询参数。

下面的例子显示了前述例子中 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()
                }
    }
}

储存授权请求

AuthorizationRequestRepository 负责 OAuth2AuthorizationRequest 的持久化,从发起授权请求到收到授权响应(回调)的过程。

OAuth2AuthorizationRequest 是用来关联和验证授权响应的。

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

如果你有一个自定义的 AuthorizationRequestRepository 的实现,你可以按如下方式配置它。

  • 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>

请求 Access Token

参见授权码授予的 Access Token Request/Response 协议流程。

授权码授予的 OAuth2AccessTokenResponseClient 的默认实现是 DefaultAuthorizationCodeTokenResponseClient,它使用 RestOperations 实例在授权服务器的Token端点将授权码交换为Access Token。

DefaultAuthorizationCodeTokenResponseClient 很灵活,因为它可以让你定制令牌请求的预处理和/或令牌响应的后处理。

自定义Access Token请求

如果你需要定制令牌请求的预处理,你可以为 DefaultAuthorizationCodeTokenResponseClient.setRequestEntityConverter() 提供一个自定义的转换器 <OAuth2AuthorizationCodeGrantRequest, RequestEntity<?>。默认的实现(OAuth2AuthorizationCodeGrantRequestEntityConverter)建立了一个标准 OAuth 2.0 Access Token 请求RequestEntity 表示。然而,提供一个自定义的 Converter 可以让你扩展标准的令牌请求并添加自定义参数。

为了只定制请求的参数,你可以为 OAuth2AuthorizationCodeGrantRequestEntityConverter.setParametersConverter() 提供一个自定义的 Converter<OAuth2AuthorizationCodeGrantRequest, MultiValueMap<String, String>> 来完全重写随请求发送的参数。这通常比直接构造一个 RequestEntity 更简单。

如果你喜欢只添加额外的参数,你可以为 OAuth2AuthorizationCodeGrantRequestEntityConverter.addParametersConverter() 提供一个自定义的 Converter<OAuth2AuthorizationCodeGrantRequest, MultiValueMap<String, String>>,它构造一个聚合的 Converter

自定义 Converter 必须返回一个有效的OAuth 2.0 Access Token 请求的 RequestEntity 表示,并能被预期的OAuth 2.0提供商理解。

定制 Access Token 响应

在另一端,如果你需要定制令牌响应的后期处理,你需要为 DefaultAuthorizationCodeTokenResponseClient.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 Access Token 请求时使用。

OAuth2AccessTokenResponseHttpMessageConverter 是一个用于OAuth 2.0 Access Token Response 的 HttpMessageConverter。你可以为 OAuth2AccessTokenResponseHttpMessageConverter.setAccessTokenResponseConverter() 提供一个自定义的 Converter<Map<String, Object>, OAuth2AccessTokenResponse>,用于将OAuth2.0 Access Token 响应参数转换为 OAuth2AccessTokenResponse

OAuth2ErrorResponseErrorHandler 是一个 ResponseErrorHandler,可以处理OAuth2.0错误,如 400 Bad Request。它使用一个 OAuth2ErrorHttpMessageConverter 将OAuth2.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>

Token 刷新

关于刷新Token的进一步细节,请参见 OAuth 2.0授权框架

刷新Access Token

参见刷新令牌授予的 Access Token Request/Response 协议流程。

刷新令牌授予的 OAuth2AccessTokenResponseClient 的默认实现是 DefaultRefreshTokenTokenResponseClient,它在授权服务器的令牌端点刷新Access Token时使用 RestOperations

DefaultRefreshTokenTokenResponseClient 很灵活,因为它可以让你定制令牌请求的预处理或令牌响应的后处理。

定制Access Token请求

如果你需要定制令牌请求的预处理,你可以为 DefaultRefreshTokenTokenResponseClient.setRequestEntityConverter() 提供一个自定义的 Converter<OAuth2RefreshTokenGrantRequest, RequestEntity<?>>。默认实现(OAuth2RefreshTokenGrantRequestEntityConverter)为标准的 OAuth 2.0 Access Token Request 建立了一个 RequestEntity 表示。然而,提供一个自定义的 Converter 可以让你扩展标准的令牌请求并添加自定义参数。

要想只定制请求的参数,你可以为 OAuth2RefreshTokenGrantRequestEntityConverter.setParametersConverter() 提供一个自定义的 Converter<OAuth2RefreshTokenGrantRequest, MultiValueMap<String, String>> 来完全覆盖随请求发送的参数。这通常比直接构造一个 RequestEntity 更简单。

如果你喜欢只添加额外的参数,你可以为 OAuth2RefreshTokenGrantRequestEntityConverter.addParametersConverter() 提供一个自定义的 Converter<OAuth2RefreshTokenGrantRequest, MultiValueMap<String, String>>,构建一个聚合的 Converter

自定义 Converter 必须返回一个有效的OAuth 2.0 Access Token Request 的 RequestEntity 表示,并能被预期的OAuth 2.0提供商理解。

定制Access Token响应

在另一端,如果你需要定制令牌响应的后期处理,你需要为 DefaultRefreshTokenTokenResponseClient.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.0Access Token请求时使用。

OAuth2AccessTokenResponseHttpMessageConverter 是一个用于OAuth 2.0 Access Token 响应的 HttpMessageConverter。你可以为 OAuth2AccessTokenResponseHttpMessageConverter.setAccessTokenResponseConverter() 提供一个自定义的 Converter<Map<String, Object>, OAuth2AccessTokenResponse>,用于将OAuth2.0 Access Token 响应参数转换为 OAuth2AccessTokenResponse

OAuth2ErrorResponseErrorHandler 是一个 ResponseErrorHandler,可以处理OAuth2.0 错误,如 400 Bad Request。它使用一个 OAuth2ErrorHttpMessageConverter 将 OAuth2.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 的授予。

OAuth2RefreshToken 可以选择性地在授权码和密码授予类型的 Access Token 响应中返回。如果 OAuth2AuthorizedClient.getRefreshToken() 可用,而 OAuth2AuthorizedClient.getAccessToken() 已过期,则会由 RefreshTokenOAuth2AuthorizedClientProvider 自动刷新。

客户端凭证

请参考 OAuth 2.0授权框架,了解关于客户端凭证授予的进一步细节。

请求 Access Token

关于客户凭证授予的进一步细节,请参见 OAuth 2.0授权框架

客户凭证授予的 OAuth2AAccessTokenResponseClient 的默认实现是 DefaultClientCredentialsTokenResponseClient,它在授权服务器的令牌端点请求 Access Token 时使用 RestOperations

DefaultClientCredentialsTokenResponseClient 很灵活,因为它可以让你定制令牌请求的预处理或令牌响应的后处理。

自定义 Access Token 请求

如果你需要定制令牌请求的预处理,你可以为 DefaultClientCredentialsTokenResponseClient.setRequestEntityConverter() 提供一个自定义的 Converter<OAuth2ClientCredentialsGrantRequest, RequestEntity<?>>。默认实现(OAuth2ClientCredentialsGrantRequestEntityConverter)为标准的 OAuth 2.0 Access Token 请求 建立了一个 RequestEntity 表示。然而,提供一个自定义的转换器可以让你扩展标准的令牌请求并添加自定义参数。

要只定制请求的参数,你可以为 OAuth2ClientCredentialsGrantRequestEntityConverter.setParametersConverter() 提供一个自定义的 Converter<OAuth2ClientCredentialsGrantRequest, MultiValueMap<String, String>> 来完全覆盖随请求发送的参数。这通常比直接构造一个 RequestEntity 更简单。

如果你喜欢只添加额外的参数,你可以为 OAuth2ClientCredentialsGrantRequestEntityConverter.addParametersConverter() 提供一个自定义的 Converter<OAuth2ClientCredentialsGrantRequest, MultiValueMap<String, String>>,它构造了一个聚合的 Converter

自定义 Converter 必须返回一个有效的OAuth 2.0 Access Token 请求的 RequestEntity 表示,并能被预期的OAuth 2.0提供商理解。

定制 Access Token 响应

在另一端,如果你需要定制令牌响应的后期处理,你需要为 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 Access Token 请求时使用。

OAuth2AccessTokenResponseHttpMessageConverter 是一个用于OAuth 2.0 Access Token 响应的 HttpMessageConverter。你可以为 OAuth2AccessTokenResponseHttpMessageConverter.setAccessTokenResponseConverter() 提供一个自定义的 Converter<Map<String, Object>, OAuth2AccessTokenResponse>,用于将OAuth2.0 Access Token 响应参数转换为 OAuth2AccessTokenResponse

OAuth2ErrorResponseErrorHandler 是一个 ResponseErrorHandler,可以处理OAuth2.0错误,如 400 Bad Request。它使用一个 OAuth2ErrorHttpMessageConverter 来转换 OAuth2.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 的实现,用于客户端凭证授予。

使用 Access Token

考虑以下Spring Boot 2.x属性,用于OAuth 2.0客户端注册。

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 都是 OPTIONAL (可选)属性。如果没有提供,通过使用 RequestContextHolder.getRequestAttributes(),它们默认为 ServletRequestAttributes

资源所有者的密码凭证

请参阅OAuth 2.0授权框架,了解关于 资源所有者密码凭证 授予的进一步细节。

请求 Access Token

参见资源所有者密码凭证授予的 Access Token Request/Response 协议流程。

资源所有者密码凭证授予的 OAuth2AccessTokenResponseClient 的默认实现是 DefaultPasswordTokenResponseClient,它在授权服务器的令牌端点请求 Access Token 时使用 RestOperations

DefaultPasswordTokenResponseClient 很灵活,因为它可以让你定制令牌请求的预处理或令牌响应的后处理。

自定义 Access Token 请求

如果你需要定制令牌请求的预处理,你可以为 DefaultPasswordTokenResponseClient.setRequestEntityConverter() 提供一个自定义的 Converter<OAuth2PasswordGrantRequest, RequestEntity<?>>。默认实现(OAuth2PasswordGrantRequestEntityConverter)为标准的 OAuth 2.0 Access Token 请求 建立了一个 RequestEntity 表示。然而,提供一个自定义的 Converter 可以让你扩展标准的令牌请求并添加自定义参数。

要只定制请求的参数,你可以为 OAuth2PasswordGrantRequestEntityConverter.setParametersConverter() 提供一个自定义的 Converter<OAuth2PasswordGrantRequest, MultiValueMap<String, String>> 来完全覆盖随请求发送的参数。这通常比直接构造一个 RequestEntity 更简单。

如果你喜欢只添加额外的参数,你可以为 OAuth2PasswordGrantRequestEntityConverter.addParametersConverter() 提供一个自定义的 Converter<OAuth2PasswordGrantRequest, MultiValueMap<String, String>>,构建一个聚合的 Converter

自定义 Converter 必须返回一个有效的OAuth 2.0 Access Token 请求的 RequestEntity 表示,并能被预期的OAuth 2.0提供商理解。

自定义 Access Token 响应

在另一端,如果你需要定制令牌响应的后期处理,你需要向 DefaultPasswordTokenResponseClient.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 Access Token 请求时使用。

OAuth2AccessTokenResponseHttpMessageConverter 是一个用于OAuth 2.0 Access Token Response 的 HttpMessageConverter。你可以为 OAuth2AccessTokenResponseHttpMessageConverter.setTokenResponseConverter() 提供一个自定义的 Converter<Map<String, String>, OAuth2AccessTokenResponse>,用来将OAuth2.0 Access Token 响应参数转换成 OAuth2AccessTokenResponse

OAuth2ErrorResponseErrorHandler 是一个 ResponseErrorHandler,可以处理OAuth2.0错误,如 400 Bad Request。它使用一个 OAuth2ErrorHttpMessageConverter 来转换OAuth2.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 的实现,用于资源所有者密码凭证授予。

使用 Access Token

考虑以下Spring Boot 2.x属性,用于OAuth 2.0客户端注册。

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 都是 OPTIONAL 属性。如果没有提供,它们默认为 ServletRequestAttributes,使用 RequestContextHolder.getRequestAttributes()

JWT Bearer

关于 JWT Bearer 授予的进一步细节,请参考OAuth 2.0客户端认证和授权授予的JSON Web Token(JWT)配置文件。

请求 Access Token

请参考 Access Token Request/Response 协议流程,了解JWT承载器的授予。

JWT Bearer 授予的 OAuth2AAccessTokenResponseClient 的默认实现是 DefaultJwtBearerTokenResponseClient,它在授权服务器的令牌端点请求 Access Token 时使用 RestOperations

DefaultJwtBearerTokenResponseClient 是相当灵活的,因为它允许你定制令牌请求的预处理和/或令牌响应的后处理。

自定义 Access Token 请求

如果你需要定制令牌请求的预处理,你可以为 DefaultJwtBearerTokenResponseClient.setRequestEntityConverter() 提供一个自定义的 Converter<JwtBearerGrantRequest, RequestEntity<?>>。默认实现 JwtBearerGrantRequestEntityConverterOAuth 2.0 Access Token Request 建立了一个 RequestEntity 表示。然而,提供一个自定义的 Converter,将允许你扩展令牌请求并添加自定义参数。

为了只定制请求的参数,你可以为 JwtBearerGrantRequestEntityConverter.setParametersConverter() 提供一个自定义的 Converter<JwtBearerGrantRequest, MultiValueMap<String, String>> 来完全重写随请求发送的参数。这通常比直接构造一个 RequestEntity 更简单。

如果你喜欢只添加额外的参数,你可以为 JwtBearerGrantRequestEntityConverter.addParametersConverter() 提供一个自定义的 Converter<JwtBearerGrantRequest, MultiValueMap<String, String>>,它构造一个聚合的 Converter

自定义 Access Token 响应

在另一端,如果你需要定制令牌响应的后处理,你需要为 DefaultJwtBearerTokenResponseClient.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 Access Token 请求时使用。

OAuth2AccessTokenResponseHttpMessageConverter 是一个用于OAuth 2.0 Access Token 响应的 HttpMessageConverter。你可以为 OAuth2AccessTokenResponseHttpMessageConverter.setAccessTokenResponseConverter() 提供一个自定义的 Converter<Map<String, Object>, OAuth2AccessTokenResponse>,用于将OAuth2.0 Access Token 响应参数转换为 OAuth2AccessTokenResponse

OAuth2ErrorResponseErrorHandler 是一个 ResponseErrorHandler,可以处理OAuth2.0的错误,例如:400 Bad Request。它使用一个 OAuth2ErrorHttpMessageConverter 将OAuth2.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)

使用 Access Token

给出以下Spring Boot 2.x属性,用于OAuth 2.0客户端注册。

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>