Spring-security: Add OAuth + WebClient Support

Created on 20 Dec 2017  路  7Comments  路  Source: spring-projects/spring-security

WebClient Support

This is now resolved. Demo usage can be found in oauth2webclient and oauth2webclient-webflux samples. A quick tour of the support:

Built In WebClient Support

WebClient has built in support for easily adding a Bearer token. For example:

webClient.get()
    .headers(h -> h.setBearerAuth(token))
    ...

Why use Spring Security Extensions?

Spring Security provides first class support for OAuth2. A few advantages of using this support are:

  • If an access token is requested and not present, Spring Security will automatically request the access token.

    • For authorization_code this involves performing the redirect and then replaying the original request

    • For client_credentials the token is simply requested and saved

  • Spring Security will automatically refresh expired tokens (if a refresh token is present)
  • Builds on other Spring Security OAuth2 support making things like using discovery of endpoints very simple
  • Users can choose to transparently include the current OAuth token or explicitly select which token should be used.

Setup

The first step is ensuring to setup the WebClient correctly.

For a Servlet environment this looks like:

@Bean
WebClient webClient(ClientRegistrationRepository clientRegistrationRepository, OAuth2AuthorizedClientRepository authorizedClientRepository) {
    ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2 = new ServletOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrationRepository, authorizedClientRepository);
    // (optional) explicitly opt into using the oauth2Login to provide an access token implicitly
    oauth2.setDefaultOAuth2AuthorizedClient(true);
    return WebClient.builder()
            .apply(oauth2.oauth2Configuration())
            .build();
}

For other (i.e. WebFlux) environments it looks like:

@Bean
WebClient webClient(ReactiveClientRegistrationRepository clientRegistrationRepository,
        ServerOAuth2AuthorizedClientRepository authorizedClientRepository) {
    ServerOAuth2AuthorizedClientExchangeFilterFunction oauth =
            new ServerOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrationRepository, authorizedClientRepository);
    // (optional) explicitly opt into using the oauth2Login to provide an access token implicitly
    oauth.setDefaultOAuth2AuthorizedClient(true);
    return WebClient.builder()
            .filter(oauth)
            .build();
}

Implicit OAuth2AuthorizedClient

If we set defaultOAuth2AuthorizedClient to true in our setup and the user authenticated with oauth2Login (i.e. OIDC), then the current authentication is used to automatically provide the access token. This is convenient, but in environments where not all endpoints should get the access token, it is dangerous (you might provide the wrong access token to an endpoint).

Mono<String> body = this.webClient
        .get()
        .uri(this.uri)
        .retrieve()
        .bodyToMono(String.class);

Explicit OAuth2AuthorizedClient

You can also explicitly provide an OAuth2AuthorizedClient by setting it on the requests attributes. In the example below we resolve the OAuth2AuthorizedClient using Spring WebFlux or Spring MVC argument resolver support. However, the user can choose to resolve the OAuth2AuthorizedClient however they wish.

@GetMapping("/explicit")
Mono<String> explicit(@RegisteredOAuth2AuthorizedClient("client-id") OAuth2AuthorizedClient authorizedClient) {
    return this.webClient
            .get()
            .uri(this.uri)
            .attributes(oauth2AuthorizedClient(authorizedClient))
            .retrieve()
            .bodyToMono(String.class);
}
````
## clientRegistrationId

Alternatively, it is possible to specify the `clientRegistrationId` on the request attributes and the `WebClient` will attempt to lookup the `OAuth2AuthorizedClient`. If it is not found, one will automatically be acquired.

```java
Mono<String> body = this.webClient
        .get()
        .uri(this.uri)
        .attributes(clientRegistrationId("client-id"))
        .retrieve()
        .bodyToMono(String.class);

Summary (original)

We should add OAuth + WebClient support. The support for using WebClient in a Servlet environment and WebFlux must be separate because:

  • The default values will be obtained from different types of contexts. For example, Spring Security's Authentication will be obtained from the SecurityContextHolder in a Servlet environment but from the ReactiveSecurityContextHolder in WebFlux.
  • In the Servlet world we must be able to save to the HttpSession which is a blocking API. The rest of the OAuth support in the Servlet world is also using a blocking API

This is going to be broken up into multiple issues:

  • #5389 - Add Bearer Token Support
  • #5545 - Add ServletOAuth2AuthorizedClientExchangeFilterFunction
  • #5386 - Add ServerOAuth2AuthorizedClientExchangeFilterFunction

    • #5413 - Add a globally applicable token

    • #4371 - Provide support for refresh_token grant

    • #5639 - Provide support for client_credentials

oauth2 enhancement

Most helpful comment

Here is some context :

Spring Security OAuth 2 had support for OAuth2RestTemplate that allowed clients to contact a Resource server by providing a ResourceDetail containing OAuth2 info.

Using WebClient requires the client to handle the token retrieval. Would be nice if that feature from OAuth2RestTemplate could be ported to Spring Security 5.

All 7 comments

Here is some context :

Spring Security OAuth 2 had support for OAuth2RestTemplate that allowed clients to contact a Resource server by providing a ResourceDetail containing OAuth2 info.

Using WebClient requires the client to handle the token retrieval. Would be nice if that feature from OAuth2RestTemplate could be ported to Spring Security 5.

@rwinch @jgrandja

I have an important request regarding that OAuth 2 support. Could it be packaged as a separate lib without the server related stuff? The main reason is that our SCS client using boot picks up Oauth2 dependencies transitively. This results in boot auto-configuring OAuth2 endpoints because the current jar has boot client and server classes altogether.

The client and server(s) will be in separate modules. The client module is in spring-security-oauth2-client and when we start the Resource Server work in Jan it will be in a separate module, for example, spring-security-oauth2-resource-server. Authorization server will also going into a separate module.

Awesome !

This is now resolved in master

Does this support authorization grant type of password?

Looking in oauth2-core/src/main/java/org/springframework/security/oauth2/core/AuthorizationGrantType.java it doesn't seem to, but I might be looking in the wrong place!

@idc101 Thanks for the feedback.

You are correct. We do not yet support password grant types. Please log an issue for support if you would like to see it

Was this page helpful?
0 / 5 - 0 ratings