此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Security 6.4.5! |
授权客户端功能
本节介绍了 Spring Security for OAuth2 客户端提供的其他功能。
解析授权客户端
这@RegisteredOAuth2AuthorizedClient
annotation 提供了将 method 参数解析为 type 为 argument 值的功能OAuth2AuthorizedClient
.
与访问OAuth2AuthorizedClient
通过使用OAuth2AuthorizedClientManager
或OAuth2AuthorizedClientService
.
以下示例演示如何使用@RegisteredOAuth2AuthorizedClient
:
-
Java
-
Kotlin
@Controller
public class OAuth2ClientController {
@GetMapping("/")
public String index(@RegisteredOAuth2AuthorizedClient("okta") OAuth2AuthorizedClient authorizedClient) {
OAuth2AccessToken accessToken = authorizedClient.getAccessToken();
...
return "index";
}
}
@Controller
class OAuth2ClientController {
@GetMapping("/")
fun index(@RegisteredOAuth2AuthorizedClient("okta") authorizedClient: OAuth2AuthorizedClient): String {
val accessToken = authorizedClient.accessToken
...
return "index"
}
}
这@RegisteredOAuth2AuthorizedClient
annotation 由OAuth2AuthorizedClientArgumentResolver
,它直接使用OAuth2AuthorizedClientManager
因此,继承了它的能力。
RestClient 集成
支持RestClient
由OAuth2ClientHttpRequestInterceptor
.
此侦听器提供了通过放置Bearer
token 中Authorization
标头。
拦截器直接使用OAuth2AuthorizedClientManager
因此继承了以下能力:
-
执行 OAuth 2.0 Access Token 请求以获取
OAuth2AccessToken
如果客户端尚未获得授权-
authorization_code
:触发授权请求重定向以启动流 -
client_credentials
:访问令牌直接从 Token Endpoint 获取 -
password
:访问令牌直接从 Token Endpoint 获取 -
通过启用扩展授权类型来支持其他授权类型
-
-
如果已有的
OAuth2AccessToken
已过期,则会刷新(或续订)
以下示例使用默认的OAuth2AuthorizedClientManager
要配置RestClient
能够通过以下方式访问受保护的资源Bearer
令牌Authorization
标头:
RestClient
跟ClientHttpRequestInterceptor
-
Java
-
Kotlin
@Configuration
public class RestClientConfig {
@Bean
public RestClient restClient(OAuth2AuthorizedClientManager authorizedClientManager) {
OAuth2ClientHttpRequestInterceptor requestInterceptor =
new OAuth2ClientHttpRequestInterceptor(authorizedClientManager);
return RestClient.builder()
.requestInterceptor(requestInterceptor)
.build();
}
}
@Configuration
class RestClientConfig {
@Bean
fun restClient(authorizedClientManager: OAuth2AuthorizedClientManager): RestClient {
val requestInterceptor = OAuth2ClientHttpRequestInterceptor(authorizedClientManager)
return RestClient.builder()
.requestInterceptor(requestInterceptor)
.build()
}
}
提供clientRegistrationId
OAuth2ClientHttpRequestInterceptor
使用ClientRegistrationIdResolver
来确定使用哪个客户端来获取 Access Token。
默认情况下,RequestAttributeClientRegistrationIdResolver
用于解析clientRegistrationId
从HttpRequest#attributes()
.
以下示例演示如何提供clientRegistrationId
via 属性:
clientRegistrationId
via 属性-
Java
-
Kotlin
import static org.springframework.security.oauth2.client.web.client.RequestAttributeClientRegistrationIdResolver.clientRegistrationId;
@Controller
public class ResourceController {
private final RestClient restClient;
public ResourceController(RestClient restClient) {
this.restClient = restClient;
}
@GetMapping("/")
public String index() {
String resourceUri = "...";
String body = this.restClient.get()
.uri(resourceUri)
.attributes(clientRegistrationId("okta")) (1)
.retrieve()
.body(String.class);
// ...
return "index";
}
}
import org.springframework.security.oauth2.client.web.client.RequestAttributeClientRegistrationIdResolver.clientRegistrationId
import org.springframework.web.client.body
@Controller
class ResourceController(private restClient: RestClient) {
@GetMapping("/")
fun index(): String {
val resourceUri = "..."
val body: String = restClient.get()
.uri(resourceUri)
.attributes(clientRegistrationId("okta")) (1)
.retrieve()
.body<String>()
// ...
return "index"
}
}
1
clientRegistrationId()
is a static
method in RequestAttributeClientRegistrationIdResolver
.
Alternatively, a custom ClientRegistrationIdResolver
can be provided.
The following example configures a custom implementation that resolves the clientRegistrationId
from the current user.
Configure ClientHttpRequestInterceptor
with custom ClientRegistrationIdResolver
-
Java
-
Kotlin
@Configuration
public class RestClientConfig {
@Bean
public RestClient restClient(OAuth2AuthorizedClientManager authorizedClientManager) {
OAuth2ClientHttpRequestInterceptor requestInterceptor =
new OAuth2ClientHttpRequestInterceptor(authorizedClientManager);
requestInterceptor.setClientRegistrationIdResolver(clientRegistrationIdResolver());
return RestClient.builder()
.requestInterceptor(requestInterceptor)
.build();
}
private static ClientRegistrationIdResolver clientRegistrationIdResolver() {
return (request) -> {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return (authentication instanceof OAuth2AuthenticationToken principal)
? principal.getAuthorizedClientRegistrationId() : null;
};
}
}
@Configuration
class RestClientConfig {
@Bean
fun restClient(authorizedClientManager: OAuth2AuthorizedClientManager): RestClient {
val requestInterceptor = OAuth2ClientHttpRequestInterceptor(authorizedClientManager)
requestInterceptor.setClientRegistrationIdResolver(clientRegistrationIdResolver())
return RestClient.builder()
.requestInterceptor(requestInterceptor)
.build()
}
fun clientRegistrationIdResolver(): ClientRegistrationIdResolver {
return ClientRegistrationIdResolver { request ->
val authentication = SecurityContextHolder.getContext().getAuthentication()
return if (authentication instanceof OAuth2AuthenticationToken) {
authentication.getAuthorizedClientRegistrationId()
} else {
null
}
}
}
}
Providing the principal
OAuth2ClientHttpRequestInterceptor
uses a PrincipalResolver
to determine which principal name is associated with the access token, which allows an application to choose how to scope the OAuth2AuthorizedClient
that is stored.
By default, SecurityContextHolderPrincipalResolver
is used to resolve the current principal
from the SecurityContextHolder
.
Alternatively, the principal
can be resolved from HttpRequest#attributes()
by configuring RequestAttributePrincipalResolver
, as the following example shows:
Configure ClientHttpRequestInterceptor
with RequestAttributePrincipalResolver
-
Java
-
Kotlin
@Configuration
public class RestClientConfig {
@Bean
public RestClient restClient(OAuth2AuthorizedClientManager authorizedClientManager) {
OAuth2ClientHttpRequestInterceptor requestInterceptor =
new OAuth2ClientHttpRequestInterceptor(authorizedClientManager);
requestInterceptor.setPrincipalResolver(new RequestAttributePrincipalResolver());
return RestClient.builder()
.requestInterceptor(requestInterceptor)
.build();
}
}
@Configuration
class RestClientConfig {
@Bean
fun restClient(authorizedClientManager: OAuth2AuthorizedClientManager): RestClient {
val requestInterceptor = OAuth2ClientHttpRequestInterceptor(authorizedClientManager)
requestInterceptor.setPrincipalResolver(RequestAttributePrincipalResolver())
return RestClient.builder()
.requestInterceptor(requestInterceptor)
.build()
}
}
The following example demonstrates providing a principal
name via attributes that scopes the OAuth2AuthorizedClient
to the application instead of the current user:
Provide principal
name via attributes
-
Java
-
Kotlin
import static org.springframework.security.oauth2.client.web.client.RequestAttributeClientRegistrationIdResolver.clientRegistrationId;
import static org.springframework.security.oauth2.client.web.client.RequestAttributePrincipalResolver.principal;
@Controller
public class ResourceController {
private final RestClient restClient;
public ResourceController(RestClient restClient) {
this.restClient = restClient;
}
@GetMapping("/")
public String index() {
String resourceUri = "...";
String body = this.restClient.get()
.uri(resourceUri)
.attributes(clientRegistrationId("okta"))
.attributes(principal("my-application")) (1)
.retrieve()
.body(String.class);
// ...
return "index";
}
}
import org.springframework.security.oauth2.client.web.client.RequestAttributeClientRegistrationIdResolver.clientRegistrationId
import org.springframework.security.oauth2.client.web.client.RequestAttributePrincipalResolver.principal
import org.springframework.web.client.body
@Controller
class ResourceController(private restClient: RestClient) {
@GetMapping("/")
fun index(): String {
val resourceUri = "..."
val body: String = restClient.get()
.uri(resourceUri)
.attributes(clientRegistrationId("okta"))
.attributes(principal("my-application")) (1)
.retrieve()
.body<String>()
// ...
return "index"
}
}
1
principal()
is a static
method in RequestAttributePrincipalResolver
.
Handling Failure
If an access token is invalid for any reason (e.g. expired token), it can be beneficial to handle the failure by removing the access token so that it cannot be used again.
You can set up the interceptor to do this automatically by providing an OAuth2AuthorizationFailureHandler
to remove the access token.
The following example uses an OAuth2AuthorizedClientRepository
to set up an OAuth2AuthorizationFailureHandler
that removes an invalid OAuth2AuthorizedClient
within the context of an HttpServletRequest
:
Configure OAuth2AuthorizationFailureHandler
using OAuth2AuthorizedClientRepository
-
Java
-
Kotlin
@Configuration
public class RestClientConfig {
@Bean
public RestClient restClient(OAuth2AuthorizedClientManager authorizedClientManager,
OAuth2AuthorizedClientRepository authorizedClientRepository) {
OAuth2ClientHttpRequestInterceptor requestInterceptor =
new OAuth2ClientHttpRequestInterceptor(authorizedClientManager);
OAuth2AuthorizationFailureHandler authorizationFailureHandler =
OAuth2ClientHttpRequestInterceptor.authorizationFailureHandler(authorizedClientRepository);
requestInterceptor.setAuthorizationFailureHandler(authorizationFailureHandler);
return RestClient.builder()
.requestInterceptor(requestInterceptor)
.build();
}
}
@Configuration
class RestClientConfig {
@Bean
fun restClient(authorizedClientManager: OAuth2AuthorizedClientManager,
authorizedClientRepository: OAuth2AuthorizedClientRepository): RestClient {
val requestInterceptor = OAuth2ClientHttpRequestInterceptor(authorizedClientManager)
val authorizationFailureHandler = OAuth2ClientHttpRequestInterceptor
.authorizationFailureHandler(authorizedClientRepository)
requestInterceptor.setAuthorizationFailureHandler(authorizationFailureHandler)
return RestClient.builder()
.requestInterceptor(requestInterceptor)
.build()
}
}
Alternatively, an OAuth2AuthorizedClientService
can be used to remove an invalid OAuth2AuthorizedClient
outside the context of an HttpServletRequest
, as the following example shows:
Configure OAuth2AuthorizationFailureHandler
using OAuth2AuthorizedClientService
-
Java
-
Kotlin
@Configuration
public class RestClientConfig {
@Bean
public RestClient restClient(OAuth2AuthorizedClientManager authorizedClientManager,
OAuth2AuthorizedClientService authorizedClientService) {
OAuth2ClientHttpRequestInterceptor requestInterceptor =
new OAuth2ClientHttpRequestInterceptor(authorizedClientManager);
OAuth2AuthorizationFailureHandler authorizationFailureHandler =
OAuth2ClientHttpRequestInterceptor.authorizationFailureHandler(authorizedClientService);
requestInterceptor.setAuthorizationFailureHandler(authorizationFailureHandler);
return RestClient.builder()
.requestInterceptor(requestInterceptor)
.build();
}
}
@Configuration
class RestClientConfig {
@Bean
fun restClient(authorizedClientManager: OAuth2AuthorizedClientManager,
authorizedClientService: OAuth2AuthorizedClientService): RestClient {
val requestInterceptor = OAuth2ClientHttpRequestInterceptor(authorizedClientManager)
val authorizationFailureHandler = OAuth2ClientHttpRequestInterceptor
.authorizationFailureHandler(authorizedClientService)
requestInterceptor.setAuthorizationFailureHandler(authorizationFailureHandler)
return RestClient.builder()
.requestInterceptor(requestInterceptor)
.build()
}
}
WebClient Integration for Servlet Environments
The OAuth 2.0 Client support integrates with WebClient
by using an ExchangeFilterFunction
.
The ServletOAuth2AuthorizedClientExchangeFilterFunction
provides a mechanism for requesting protected resources by using an OAuth2AuthorizedClient
and including the associated OAuth2AccessToken
as a Bearer Token.
It directly uses an OAuth2AuthorizedClientManager
and, therefore, inherits the following capabilities:
-
An OAuth2AccessToken
is requested if the client has not yet been authorized.
-
authorization_code
: Triggers the Authorization Request redirect to initiate the flow.
-
client_credentials
: The access token is obtained directly from the Token Endpoint.
-
password
: The access token is obtained directly from the Token Endpoint.
-
If the OAuth2AccessToken
is expired, it is refreshed (or renewed) if an OAuth2AuthorizedClientProvider
is available to perform the authorization
The following code shows an example of how to configure WebClient
with OAuth 2.0 Client support:
-
Java
-
Kotlin
@Bean
WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {
ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client =
new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
return WebClient.builder()
.apply(oauth2Client.oauth2Configuration())
.build();
}
@Bean
fun webClient(authorizedClientManager: OAuth2AuthorizedClientManager?): WebClient {
val oauth2Client = ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager)
return WebClient.builder()
.apply(oauth2Client.oauth2Configuration())
.build()
}
Providing the Authorized Client
The ServletOAuth2AuthorizedClientExchangeFilterFunction
determines the client to use (for a request) by resolving the OAuth2AuthorizedClient
from the ClientRequest.attributes()
(request attributes).
The following code shows how to set an OAuth2AuthorizedClient
as a request attribute:
-
Java
-
Kotlin
@GetMapping("/")
public String index(@RegisteredOAuth2AuthorizedClient("okta") OAuth2AuthorizedClient authorizedClient) {
String resourceUri = ...
String body = webClient
.get()
.uri(resourceUri)
.attributes(oauth2AuthorizedClient(authorizedClient)) (1)
.retrieve()
.bodyToMono(String.class)
.block();
...
return "index";
}
@GetMapping("/")
fun index(@RegisteredOAuth2AuthorizedClient("okta") authorizedClient: OAuth2AuthorizedClient): String {
val resourceUri: String = ...
val body: String = webClient
.get()
.uri(resourceUri)
.attributes(oauth2AuthorizedClient(authorizedClient)) (1)
.retrieve()
.bodyToMono()
.block()
...
return "index"
}
1
oauth2AuthorizedClient()
is a static
method in ServletOAuth2AuthorizedClientExchangeFilterFunction
.
The following code shows how to set the ClientRegistration.getRegistrationId()
as a request attribute:
-
Java
-
Kotlin
@GetMapping("/")
public String index() {
String resourceUri = ...
String body = webClient
.get()
.uri(resourceUri)
.attributes(clientRegistrationId("okta")) (1)
.retrieve()
.bodyToMono(String.class)
.block();
...
return "index";
}
@GetMapping("/")
fun index(): String {
val resourceUri: String = ...
val body: String = webClient
.get()
.uri(resourceUri)
.attributes(clientRegistrationId("okta")) (1)
.retrieve()
.bodyToMono()
.block()
...
return "index"
}
1
clientRegistrationId()
is a static
method in ServletOAuth2AuthorizedClientExchangeFilterFunction
.
The following code shows how to set an Authentication
as a request attribute:
-
Java
-
Kotlin
@GetMapping("/")
public String index() {
String resourceUri = ...
Authentication anonymousAuthentication = new AnonymousAuthenticationToken(
"anonymous", "anonymousUser", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS"));
String body = webClient
.get()
.uri(resourceUri)
.attributes(authentication(anonymousAuthentication)) (1)
.retrieve()
.bodyToMono(String.class)
.block();
...
return "index";
}
@GetMapping("/")
fun index(): String {
val resourceUri: String = ...
val anonymousAuthentication: Authentication = AnonymousAuthenticationToken(
"anonymous", "anonymousUser", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS"))
val body: String = webClient
.get()
.uri(resourceUri)
.attributes(authentication(anonymousAuthentication)) (1)
.retrieve()
.bodyToMono()
.block()
...
return "index"
}
1
authentication()
is a static
method in ServletOAuth2AuthorizedClientExchangeFilterFunction
.
It is recommended to be cautious with this feature since all HTTP requests will receive an access token bound to the provided principal.
Defaulting the Authorized Client
If neither OAuth2AuthorizedClient
or ClientRegistration.getRegistrationId()
is provided as a request attribute, the ServletOAuth2AuthorizedClientExchangeFilterFunction
can determine the default client to use, depending on its configuration.
If setDefaultOAuth2AuthorizedClient(true)
is configured and the user has authenticated by using HttpSecurity.oauth2Login()
, the OAuth2AccessToken
associated with the current OAuth2AuthenticationToken
is used.
The following code shows the specific configuration:
-
Java
-
Kotlin
@Bean
WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {
ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client =
new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
oauth2Client.setDefaultOAuth2AuthorizedClient(true);
return WebClient.builder()
.apply(oauth2Client.oauth2Configuration())
.build();
}
@Bean
fun webClient(authorizedClientManager: OAuth2AuthorizedClientManager?): WebClient {
val oauth2Client = ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager)
oauth2Client.setDefaultOAuth2AuthorizedClient(true)
return WebClient.builder()
.apply(oauth2Client.oauth2Configuration())
.build()
}
Be cautious with this feature, since all HTTP requests receive the access token.
Alternatively, if setDefaultClientRegistrationId("okta")
is configured with a valid ClientRegistration
, the OAuth2AccessToken
associated with the OAuth2AuthorizedClient
is used.
The following code shows the specific configuration:
-
Java
-
Kotlin
@Bean
WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {
ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client =
new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
oauth2Client.setDefaultClientRegistrationId("okta");
return WebClient.builder()
.apply(oauth2Client.oauth2Configuration())
.build();
}
@Bean
fun webClient(authorizedClientManager: OAuth2AuthorizedClientManager?): WebClient {
val oauth2Client = ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager)
oauth2Client.setDefaultClientRegistrationId("okta")
return WebClient.builder()
.apply(oauth2Client.oauth2Configuration())
.build()
}
Be cautious with this feature, since all HTTP requests receive the access token.