Spring OAuth2 Using Password Grant Type With Additional Headers

Part 1 – Getting the Access Token

In this example, I am using the older Spring Security library which is: spring-security-oauth2

The JAR file can be found at Maven public repositories here.

Dependency

For my case, I am specifically using the release version below because I am trying to match some limitations (Such as what are available for download from a private repository). Then add it via gradle or some other such tool.

implementation group: 'org.springframework.security.oauth', name: 'spring-security-oauth2', version: '2.3.8.RELEASE'

Access Token Request Details

The specifics for my use-case are simple. I was given a bunch of information to be able to request an access token from a 3rd party service token provider. The token will be needed to call/use other 3rd party API endpoints.

These information are:

  • Username
  • Password
  • Client ID
  • Client Secret
  • Grant Type
  • Token endpoint (URL)

Then there is an additional security layer so that access to this resource is further limited only to users that are members of a group. This security layer is nothing more than a simple gateway. It uses Basic Auth. It only needs 2 things:

  • Gateway Username
  • Gateway Password

The gateway layer is not part of Oauth2 specifications, mind you. Many implementations won’t have that and is not necesarily required. Then again it is also quite common to have a gateway for an application that is within a private network to use so it can gain access to the outside world.

With all the above, I can now submit a request for an access token given the credentials at the provided Token Endpoint. It is a POST request via a form.

Implementation

I started with a Configuration class in my Spring Boot project. It will look something like this:

@Configuration
@EnableOAuth2Client
public class Oauth2ClientPropertiesConfiguration {

    @Bean
    protected OAuth2ProtectedResourceDetails resource() {
        ResourceOwnerPasswordResourceDetails resource;
        resource = new ResourceOwnerPasswordResourceDetails();
        resource.setAccessTokenUri("https://token/url/somewhere");
        resource.setClientAuthenticationScheme(AuthenticationScheme.form);
        resource.setClientId("my-client-id");
        resource.setClientSecret("my-client-secret);
        resource.setGrantType("password");
        resource.setUsername(oauth2Username);
        resource.setPassword(oauth2Password);
        return resource;
    }

    @Bean
    public OAuth2RestOperations restTemplate() {
        /** define Authorization header here **/
        String basicHeader = new StringBuilder().append("Basic").append(" ").append("aGVsbG86d29ybGQ=").toString();
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.add("Authorization", basicHeader);
        /** end **/

        AccessTokenRequest tokenRequest = new DefaultAccessTokenRequest();
        OAuth2RestTemplate restTemplate = new OAuth2RestTemplate(resource(), new DefaultOAuth2ClientContext(tokenRequest));
        CustomAccessTokenProvider accessTokenProvider = new CustomAccessTokenProvider(httpHeaders);

        /** set the Custom Token Provider here **/
        restTemplate.setAccessTokenProvider(accessTokenProvider);

        return restTemplate;

}

Okay, so the above is pretty straightforward. We annotate this class with @Configuration so we can wire this anywhere else in our application. Then we also add the @EnableOAuth2Client annotation which tells Spring that this is a configuration for an OAuth2 client.

Next is the a bean for the ResourceOwnerPasswordResourceDetails. Our resource here is the 3rd party token provider. This is where the username, password, and other details are set. I am specifically setting the AuthenticationScheme for the client to AuthenticationScheme.form too.

Then we have a bean for OAuth2RestOperations interface class. That is actually implementing the RestTemplate class implementation. Later on we can use this bean to get an access token.

That’s about it. But wait, there are other settings in there that have been commented. What are those?

Those are there because I want the OAuth2 client to add the Authorization header for Basic Auth for the gateway security layer whenever it requests for a token. If there is no gateway, then I can remove those lines.

What I did was to extend the ResourceOwnerPasswordAccessTokenProvider class so I can override some methods it has. This provider matches the first bean we created since I am using a password grant type of resource.

I can define this custom provider class as such:

public class CustomAccessTokenProvider extends ResourceOwnerPasswordAccessTokenProvider {
        private HttpHeaders httpHeaders;

        public CustomAccessTokenProvider(HttpHeaders httpHeaders) {
            this.httpHeaders = httpHeaders;
        }

        @Override
        public OAuth2AccessToken obtainAccessToken(OAuth2ProtectedResourceDetails details, AccessTokenRequest request)
                throws UserRedirectRequiredException, AccessDeniedException, OAuth2AccessDeniedException {
            ResourceOwnerPasswordResourceDetails resource = (ResourceOwnerPasswordResourceDetails) details;
            return retrieveToken(request, resource, getParametersForTokenRequest(resource, request), getHeaders());

        }

        private HttpHeaders getHeaders() {
            if (httpHeaders == null) {
                return new HttpHeaders();
            }
            return this.httpHeaders;
        }

        private MultiValueMap<String, String> getParametersForTokenRequest(ResourceOwnerPasswordResourceDetails resource, AccessTokenRequest request) {
            MultiValueMap<String, String> form = new LinkedMultiValueMap<String, String>();
            form.set("grant_type", "password");
            form.set("username", resource.getUsername());
            form.set("password", resource.getPassword());
            form.putAll(request);
            if (resource.isScoped()) {
                StringBuilder builder = new StringBuilder();
                List<String> scope = resource.getScope();

                if (scope != null) {
                    Iterator<String> scopeIt = scope.iterator();
                    while (scopeIt.hasNext()) {
                        builder.append(scopeIt.next());
                        if (scopeIt.hasNext()) {
                            builder.append(' ');
                        }
                    }
                }

                form.set("scope", builder.toString());
            }
            return form;
        }
    }

To break it down, I am overriding the obtainAccessToken method of the parent class with my own. I only want to add HTTP headers that I’ve specified when using this provider. Notice that the constructor of this class takes in a HttpHeaders data structure. I can define as many additional headers as I want and put it in there.

Then there is also the getParametersForTokenRequest method. I merely lifted this from the parent class implementation. Why? I’ve extended it right? Yes, true. But because the parent has this particular method set to private. I therefore am not able to access it directly. Luckily, the code is available.

Now that everything is set, we can instantiate this custom class, add the headers, then set it to the OAuth2 client configuration’s OAuth2RestTemplate.

How to use?

I can then wire the configuration class defined earlier like so:

@Autowired
private OAuth2RestOperations restTemplate;

Then call the method to get the access token simply by invoking:

 restTemplate.getAccessToken()

An example method can have the following for example:

    public String getAccessToken() {
        boolean isExpired = restTemplate.getAccessToken().isExpired();
        String expiration = restTemplate.getAccessToken().getExpiration().toInstant().toString();
        String token = restTemplate.getAccessToken().getValue();
        log.info("Token: {}, Expiration: {}, isExpired: {}", token, expiration, isExpired);
        return token;
    }

Continue to Part 2Spring OAuth2 Using Password Grant Type With Additional Headers Continued

Similar Posts:

Notice: This article was published on February 24, 2021 and the content above may be out of date.