I'm working on a little proof of concept for a set of endpoints that need to be able to call each other passing tokens which are obtained via an OAuth 2 client credentials flow. I'm using Spring Boot and related projects to build these endpoints, and I'm confused as to why the framework appears to be very opinionated about the following code:
package com.example.client;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.client.OAuth2ClientContext;
import org.springframework.security.oauth2.client.OAuth2RestOperations;
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails;
import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableOAuth2Client;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@Configuration
@EnableAutoConfiguration
@EnableOAuth2Client
@RestController
public class StuffClient {
@Value("${security.oauth2.client.access-token-uri}")
private String tokenUrl;
@Value("${security.oauth2.client.id}")
private String clientId;
@Value("${security.oauth2.client.client-secret}")
private String clientSecret;
@Value("${security.oauth2.client.grant-type}")
private String grantType;
@Autowired
private OAuth2RestOperations restTemplate;
private String uri = "http://localhost:8082/stuff/";
@RequestMapping(value = "/client/{stuffName}", method = RequestMethod.GET)
public String client(@PathVariable("stuffName") String stuffName) {
String request = uri + stuffName;
return restTemplate.getForObject(request, String.class);
}
@Bean
public OAuth2RestOperations restTemplate(OAuth2ClientContext clientContext) {
return new OAuth2RestTemplate(resource(), clientContext);
}
@Bean
protected OAuth2ProtectedResourceDetails resource() {
ClientCredentialsResourceDetails resource = new ClientCredentialsResourceDetails();
resource.setAccessTokenUri(tokenUrl);
resource.setClientId(clientId);
resource.setClientSecret(clientSecret);
resource.setGrantType(grantType);
return resource;
}
}
And the accompanying configuration file:
server:
port: 8081
security:
basic:
enabled: false
oauth2:
client:
id: test-client
client-secret: test-secret
access-token-uri: http://localhost:8080/uaa/oauth/token
grant-type: client_credentials
The above works exactly as expected. If I change security.oauth2.client.id
to security.oauth2.client.client-id
(in both the Java code and the YAML), I get a 500 error, the first line of which is:
org.springframework.security.oauth2.client.resource.OAuth2AccessDeniedException: Unable to obtain a new access token for resource 'null'. The provider manager is not configured to support it.
The code also works fine if I hard code values for all of the instance variables. It seems to work fine, in fact, in every permutation of populating those instance variables except the one where I use @Value
to populate clientId
with the value of security.oauth2.client.client-id
So my main question is: is the framework actually opinionated in this very specific way? And if so, why? And, can I leverage this opinionated-ness to simplify my code?
I am not sure which spring-boot version you are using. I am using spring-boot version 1.5.4.RELEASED
and to simplify your codes,
you can inject OAuth2ProtectedResourceDetails like
@Autowired
private OAuth2ProtectedResourceDetails resource;
and create OAuth2RestTemplate as
@Bean
@Primary
public OAuth2RestOperations restTemplate(OAuth2ClientContext clientContext) {
return new OAuth2RestTemplate(resource, clientContext);
}
sample yaml ..
### OAuth2 settings ###
security:
user:
password: none
oauth2:
client:
accessTokenUri: ${auth-server}/oauth/token
userAuthorizationUri: ${auth-server}/oauth/authorize
clientId: myclient
clientSecret: secret
resource:
user-info-uri: ${auth-server}/sso/user
jwt:
keyValue: |
-----BEGIN PUBLIC KEY-----
your public key
-----END PUBLIC KEY-----
And then,use restTemplate
instance in controllers as
@Autowired
private OAuth2RestOperations restTemplate;
I hope some helps for you.