1

We currently have several Spring Boot applications connecting to other services using a service account. Till now we used the AccessTokenRequest of the OAuth2ClientContext on a RestTemplate to put the user and password of the service account in and used the returned OAuth token to connect to the other services.

Right now we're building a new application using Spring Boot 5.2 and as the new Spring Security OAuth should be used now the separate OAuth library has become deprecated, we would also like to replace the RestTemplate solution with a WebClient one as the RestTemplate will become deprecated in the near future as well. I've tried several approaches of retrieving the token, but couldn't find a solution that works.

I found set-ups like the one mentioned on Spring Security 5 Replacement for OAuth2RestTemplate, but no way of putting a username and password inside the WebClient. I found other approaches using a ClientRegistrationRepository instead of a ReactiveClientRegistrationRepository and some of those approaches actually have options (like How to re-initialize password grant in Spring security 5.2 OAuth) of putting a username and password in an AuthorizedClientManager that gets to be a parameter when instantiating the filter, but somehow I always end up with the message that no Bean of the ClientRegistrationRepository could be found, no matter what properties I put in the application.yaml (maybe this doesn't work because the application is an MVC application instead of a WebFlux one?)

I know that I need to set the authorization-grant-type to be 'password', but there's already someone else asking how to get that working (Spring Security 5.2.1 + spring-security-oauth2 + WebClient: how to use password grant-type) and no answers to that question, yet.

So ... did they 'deprecate' this easy way of using a username and password to retrieve a token and use that token to connect to another service in Spring Security 5.2? And if yes, what should be used now?

JeroenV
  • 53
  • 2
  • 10

2 Answers2

2

Yes, RestTemplate and OAuthRestTemplate are all deprecated. WebClient supports OAuth out of the box. You don't need to do anything special or add any headers yourself. It's actually extremely trivial.

Create a configuration class that exposes a WebClient bean, make sure you take the client repository as a param. In the method, you pass the repo into a filter function:

@Bean
public WebClient webClient(ReactiveClientRegistrationRepository clientRegistrations) {
        return WebClient.builder().filter(filterFunction(clientRegistrations))
                                  .baseUrl(String.format("http://%s:8080", getHostName()))
                                  .build();

}

    private ServerOAuth2AuthorizedClientExchangeFilterFunction filterFunction(ReactiveClientRegistrationRepository clientRegistrations) {
        ServerOAuth2AuthorizedClientExchangeFilterFunction filterFunction = new ServerOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrations, new UnAuthenticatedServerOAuth2AuthorizedClientRepository());
        filterFunction.setDefaultClientRegistrationId("myKey");
        return filterFunction;
    }

NOTE: In the filter function, replace the "myKey" with something that matches the following property structure in application.properties (replace myKey in the property paths with your name):

spring.security.oauth2.client.registration.myKey.authorization-grant-type=password
spring.security.oauth2.client.registration.myKey.client-id=xxx
spring.security.oauth2.client.registration.myKey.client-secret=xxx

spring.security.oauth2.client.provider.myKey.token-uri=http://localhost:8080/oauth/token

Aaaannddd.... you're done! OAuth token refresh is also built in.

SledgeHammer
  • 7,338
  • 6
  • 41
  • 86
  • Well, first of all: there’s no getHostName() that Intellij understands, so my guess is you’ve got an extra method in the same file. Second, in the old code a client_id (yes, underscore, not hyphen, I just saw that) is put as attribute on the AccessTokenRequest and put in the OAuthClientContext, I thought I needed to use that very same as client-id and third, I still don’t see where the username and password of the service account should be placed, we’re not working with secrets. We’re using spring 5.2.2 btw, don’t know if that makes a difference – JeroenV Jan 14 '20 at 09:13
  • 2
    @JeroenV the getHostName() method is just my plumbing for getting the host name. You can pull the server url from wherever you want. You put your username and password in client-id and client-secret. It's the same for a password grant vs. client credentials grant. If you understand the mechanics of oAuth, they are basically the same. Yes, I'm on the latest Spring / Spring Boot. This is the latest setup for Spring Security 5. – SledgeHammer Jan 14 '20 at 13:59
  • 1
    @JeroenV To expand on "they are the same", its just Spring Security that puts them in the same place in the application.properties. In reality client credentials vs. password flow grants have identical mechanics, just different key names in the HTTP request. – SledgeHammer Jan 14 '20 at 14:07
0

Well, it turned out to be a little bit different. A comment on the last SO question I linked requested the author to use debugging in the PasswordOAuth2AuthorizedClientProvider to see what went on / wrong. So I started debugging as well and with the setup you provided there are 4 Providers for 4 login types that are supplied, but out of the 4 it's not the PasswordReactiveOAuth2AuthorizedProvider that's used, and also not the ClientCredentialsReactiveOAuth2AuthorizedProvider or RefreshTokenReactiveOAuth2AuthorizedProvider one, but the AuthorizationCodeReactiveOAuth2AuthorizedProvider is called and that's quite weird when authorization-grant-type is set to password. That seems like a bug to me ...

Anyway, I found another SO question from rigon with a problem a bit different than mine, but close enough: Create route in Spring Cloud Gateway with OAuth2 Resource Owner Password grant type and he provided code I could get working as the Provider used with that codebase is in fact the PasswordReactiveOAuth2AuthorizedClientProvider

In the end I only needed to enter 3 items in the yaml-file: the client-id (with the client_id that used to be put as attribute on the AccessTokenRequest), the authorization-grant-type and the token-uri Besides that I copied the WebClient and ReactiveOAuth2AuthorizedClientManager setup from the code provided by rigon and put the username and password from the configuration file into the settings (I left the seperate contextAttributesMapper out as I only needed to provide 2 context parameters directly into a map).

JeroenV
  • 53
  • 2
  • 10
  • If one is trying to access Salesforce, this sample worked well for me: https://www.reddit.com/r/javahelp/comments/jpzg3o/howto_use_spring_boot_webclient_to_access_an/ – Glen Mazza Mar 14 '21 at 20:45