2

I am new to Spring Security and I want to implement a client for a OAUTH2 secured service that only accepts password grant.

Obtaining the access_token from the auth server is done using data in the http body like this:

client_id={{clientId}}&client_secret={{client_secret}}&grant_type=password&username={{username}}&password={{password}}

Afterwards the access_token must be used in the header field Authorization to access the actual service. (e.g. Authorization=Bearer <access_token>)

My goal is to use the provided features from Spring Security OAuth2 to request an access_token from the auth service, and use it for accessing the service endpoints until token expiration. I also like to have that my access_token is automatically refreshed using the refresh_token value from the auth server. I want to achieve this while fully utilizing Spring's features.

I found that I can use OAuth2RestTemplate with ResourceOwnerPasswordResourceDetails for the grant_type password.

The StackOverflow post oAuth2 client with password grant in Spring Security was very helpful for me, but I have not got it to work. I also found the post Authentication is required to obtain an access token (anonymous not allowed) where a user encountered the same exception, but uses client_credentials and AuthorizationCodeResourceDetails.

At the moment my code looks like this.

@Service
public class MyClient {

    @Autowired
    private OAuth2RestTemplate restTemplate;

    @Value("${authServer.accessTokenUri}")
    private String accessTokenUri;

    @Value("${authServer.clientId}")
    private String clientId;

    @Value("${authServer.clientSecret}")
    private String clientSecret;

    @Value("${authServer.username}")
    private String username;

    @Value("${authServer.password}")
    private String password;

    @Value("${serviceUrl}")
    private String serviceUrl;


    @Bean
    public OAuth2RestTemplate restTemplate(OAuth2ClientContext oauth2ClientContext) {
        OAuth2RestTemplate template = new OAuth2RestTemplate(resource(), oauth2ClientContext);
        template.setAccessTokenProvider(accessTokenProvider());
        return template;
    }

    @Bean
    public AccessTokenProvider accessTokenProvider() {
        ResourceOwnerPasswordAccessTokenProvider tokenProvider = new ResourceOwnerPasswordAccessTokenProvider();
        return new AccessTokenProviderChain(
                Arrays.<AccessTokenProvider>asList(tokenProvider)
        );
    }

    @Bean
    protected OAuth2ProtectedResourceDetails resource() {
        ResourceOwnerPasswordResourceDetails resource = new ResourceOwnerPasswordResourceDetails();
        resource.setId(clientId);
        resource.setAccessTokenUri(accessTokenUri);
        resource.setClientId(clientId);
        resource.setClientSecret(clientSecret);
        resource.setGrantType("password");
        resource.setClientAuthenticationScheme(AuthenticationScheme.form); // fetch access_token by sending authentication data in HTTP Body
        resource.setAuthenticationScheme(AuthenticationScheme.header); // send access_token via HTTP Header 'Bearer' field when accessing actual service
        resource.setUsername(username);
        resource.setPassword(password);
        return resource;
    }


    public void getDataFromService() {
        String response = restTemplate.getForObject(serviceUrl, String.class);
    }

}

An exception is thrown in AccessTokenProviderChain, because of this block.

if (auth instanceof AnonymousAuthenticationToken) {
    if (!resource.isClientOnly()) {
        throw new InsufficientAuthenticationException("Authentication is required to obtain an access token (anonymous not allowed)");
    }
}

Here is the exception stack trace.

org.springframework.security.authentication.InsufficientAuthenticationException: Authentication is required to obtain an access token (anonymous not allowed)
    at org.springframework.security.oauth2.client.token.AccessTokenProviderChain.obtainAccessToken(AccessTokenProviderChain.java:91) ~[spring-security-oauth2-2.3.4.RELEASE.jar:na]
    at org.springframework.security.oauth2.client.OAuth2RestTemplate.acquireAccessToken(OAuth2RestTemplate.java:221) ~[spring-security-oauth2-2.3.4.RELEASE.jar:na]
    at org.springframework.security.oauth2.client.OAuth2RestTemplate.getAccessToken(OAuth2RestTemplate.java:173) ~[spring-security-oauth2-2.3.4.RELEASE.jar:na]
    at org.springframework.security.oauth2.client.OAuth2RestTemplate.createRequest(OAuth2RestTemplate.java:105) ~[spring-security-oauth2-2.3.4.RELEASE.jar:na]
    at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:731) ~[spring-web-5.1.7.RELEASE.jar:5.1.7.RELEASE]
    at org.springframework.security.oauth2.client.OAuth2RestTemplate.doExecute(OAuth2RestTemplate.java:128) ~[spring-security-oauth2-2.3.4.RELEASE.jar:na]
    at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:670) ~[spring-web-5.1.7.RELEASE.jar:5.1.7.RELEASE]
    at org.springframework.web.client.RestTemplate.getForObject(RestTemplate.java:311) ~[spring-web-5.1.7.RELEASE.jar:5.1.7.RELEASE]

As you can see I cannot request an access_token. I do not understand why I get this exception, because if I directly request an access_token from the auth server using the curl command, I am able to authenticate using only the provided data as stated.

I manually obtained an access_token successfully like this, when adding the following code before invoking restTemplate.getForObject(...).

ResourceOwnerPasswordAccessTokenProvider accessTokenProvider = new ResourceOwnerPasswordAccessTokenProvider();
OAuth2AccessToken token = accessTokenProvider.obtainAccessToken(resource(), new DefaultAccessTokenRequest());
restTemplate.getOAuth2ClientContext().setAccessToken(token);
String token = restTemplate.getAccessToken();

But, manually obtaining the access_token is not that what I want. Is there something I am missing? Is it possible to automatically obtain an access_token and refresh it using Spring Security with password grant? Although checking code multiple hours on Github, StackOverflow etc. ... I have not been able to get my code to work.


UPDATE:

I found that my ResourceOwnerPasswordResourceDetails instance inside my OAuth2RestTemplate instance is not initialized, when I want to make use of it inside getDataFromService(). (i.e. the fields like username are null). After clarification and help from @JoeGrandja, my question now does not really target Spring Security, but rather Spring.

What can I do to make use of the @Value annotations inside a @Bean annotated method. At the moment, when the restTemplate is constructed using the @Bean annotated method resource(), the values from the application.yml are obviously not available yet.

Bernhard
  • 703
  • 5
  • 25
  • The reason you're getting the error "Authentication is required to obtain an access token (anonymous not allowed)" is because you have not authenticated with the application - hence the anonymous user error. A user must be authenticated with the application in order for the user to grant a client access and obtain an access token. The authenticated user's credentials is used as the params for `username` and `password` for the token request via `ResourceOwnerPasswordResourceDetails` – Joe Grandja Jun 10 '19 at 15:18
  • @JoeGrandja But, as you can see in my code, I already set the `username` and `password` inside my `ResourceOwnerPasswordResourceDetails` instance. See method `resource()`. Therefore, I am confident that I provide enough data such that the authentication should work. What do I miss? – Bernhard Jun 10 '19 at 16:22
  • 1
    Yes, I do see that you're setting username/password in `resource()` which is used for the Token Request. However, I'm referring to the application that uses `OAuth2RestTemplate` and associated `ResourceOwnerPasswordResourceDetails`. To be clear, you must be authenticated to the application and it looks like the code path that uses `OAuth2RestTemplate` is an anonymous request and therefore you are not authenticated with the application. – Joe Grandja Jun 10 '19 at 17:59
  • @JoeGrandja Well, you pointed me into a right direction. I just found that my `ResourceOwnerPasswordResourceDetails` instance inside the `OAuth2RestTemplate` instance is not initialized, when calling the actual service (see `getDataFromService()`). My `@Value` annotated fields (injected from application.yml) may not have a value set, when the restTemplate Bean is constructed using the `resource()` method. Do you have any advice, how I can get the values from the yml file before the Bean construction, such that the restTemplate instance has the initialized ResourceDetails? – Bernhard Jun 10 '19 at 19:44
  • 1
    The `MyClient` class is not configured correctly. `@Bean` should be defined in `@Configuration` classes, however, you have annotated the class with `@Service`. Please review the reference docs to understand how Java based configuration works -> https://docs.spring.io/spring/docs/5.1.7.RELEASE/spring-framework-reference/core.html#beans-java-basic-concepts – Joe Grandja Jun 11 '19 at 15:43
  • @JoeGrandja Thank you so much! I have not checked out this chapter yet. I will definitely catch up on that topic! I was able to accomplish a working solution, with your help. I appreciate your support! :) – Bernhard Jun 11 '19 at 19:51

2 Answers2

2

I found a solution with the help and support of @JoeGrandja. Thank you very much! :)

If anyone else has problems, here is my working solution. I also recommend reading the comments from @JoeGrandja above.

@Configuration
@ConfigurationProperties(prefix = "authserver")
public class AuthServerConfigProperties {

    private String accessTokenUri;
    private String clientId;
    private String grantType;
    private String clientSecret;
    private String username;
    private String password;

   // Getter & Setter for all properties ...
}


@Configuration
public class CommConfig {

    @Autowired
    AuthServerConfigProperties configProperties;

    @Bean
    public OAuth2RestOperations restTemplate(OAuth2ClientContext oauth2ClientContext) {
        OAuth2RestTemplate oAuth2RestTemplate = new OAuth2RestTemplate(resource(), oauth2ClientContext);
        oAuth2RestTemplate.setAccessTokenProvider(new ResourceOwnerPasswordAccessTokenProvider());
        return oAuth2RestTemplate;
    }

    @Bean
    protected OAuth2ProtectedResourceDetails resource() {
        ResourceOwnerPasswordResourceDetails resource = new ResourceOwnerPasswordResourceDetails();
        resource.setId(configProperties.getClientId()); // not necessary
        resource.setAccessTokenUri(configProperties.getAccessTokenUri());
        resource.setClientId(configProperties.getClientId());
        resource.setClientSecret(configProperties.getClientSecret());
        resource.setGrantType(configProperties.getGrantType());
        resource.setClientAuthenticationScheme(AuthenticationScheme.form); // fetch access_token by sending authentication data in HTTP Body
        resource.setAuthenticationScheme(AuthenticationScheme.header); // send access_token via HTTP Header 'Bearer' field when accessing actual service
        resource.setUsername(configProperties.getUsername());
        resource.setPassword(configProperties.getPassword());
        return resource;
    }
}


@RestController
public class MyController {

    @Autowired
    private OAuth2RestOperations restTemplate;

    @Value("${serviceUrl}")
    private String serviceUrl;

    @RequestMapping(value = "/getData", method = RequestMethod.GET)
    @ResponseBody
    public ResponseEntity<String> getData() {
        String response = restTemplate.getForObject(serviceUrl, String.class);
        return new ResponseEntity(response, HttpStatus.OK);
    }
}
Bernhard
  • 703
  • 5
  • 25
  • In your solution, you removed AccessTokenProviderChain and used directly ResourceOwnerPasswordAccessTokenProvider. And when I look at the implementation of ResourceOwnerPasswordAccessTokenProvider, it doesn't use refreshToken to get accessToken if it expires. But AccessTokenProviderChain makes it. Just information who is going to use this solution. – cmlonder Nov 01 '19 at 11:32
0

I had a similar problem: rest request was anonymous, but internal processing required oauth2 authorization, resolved with a simple extend:

public class CustomResourceOwnerPasswordResourceDetails extends ResourceOwnerPasswordResourceDetails {
    @Override
    public boolean isClientOnly() {
        return true;
    }
}
Dharman
  • 30,962
  • 25
  • 85
  • 135
francarl
  • 921
  • 1
  • 8
  • 12