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?
Before making a request to the resource server, first check if the token has already expired or is about to expire. If so, request a new token. Finally, make the request to the resource server. Save the token and expiration time in memory, and have a timer which triggers a token refresh some interval before expiry.
After project Spring Security OAuth has been deprecated, there was a lot of confusion in the community. You could use Spring Security to write the resource server but not the authorization server. But the dark age is now over.
Spring Security and Spring Boot permit to quickly set up a complete OAuth2 authorization/authentication server in an almost declarative manner. The setup can be further shortened by configuring OAuth2 client's properties directly from application. properties/yml file, as explained in this tutorial.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With