I am new to spring security, and i come across to implement OAuth2 with client_credentials as Grant type.
i am using below piece of code, but i am getting suggestion that ClientCredentialsResourceDetails, OAuth2RestTemplate & OAuth2AccessToken are deprecated.
Can someone help with the alternate to this ?
private String getAuthTocken(){
final ClientCredentialsResourceDetails resourceDetails = new ClientCredentialsResourceDetails();
resourceDetails.setClientId("ceapiClientId");
resourceDetails.setClientSecret("ceapiClientSecret");
resourceDetails.setGrantType("client_credentials");
resourceDetails.setAccessTokenUri("https://auth.abcdcommerce.com/oauth-server/oauth/token");
final OAuth2RestTemplate oAuth2RestTemplate = new OAuth2RestTemplate(resourceDetails);
final OAuth2AccessToken accessToken = oAuth2RestTemplate.getAccessToken();
final String accessTokenAsString = accessToken.getValue();
return accessTokenAsString;
}
The alternative is to use the new non-blocking WebClient or a RestTemplate with an interceptor over the deprecated OAuthRestTemplate. Everything in the spring-security-oauth artifacts has an end of life road map.
https://spring.io/blog/2019/11/14/spring-security-oauth-2-0-roadmap-update
https://github.com/spring-projects/spring-security/wiki/OAuth-2.0-Features-Matrix
The migration guide can be found here,
https://github.com/spring-projects/spring-security/wiki/OAuth-2.0-Migration-Guide
From the migration guide,
Spring Security chooses to favor composition and instead exposes an OAuth2AuthorizedClientService, which is useful for creating RestTemplateinterceptors or WebClient exchange filter functions. Spring Security provides ExchangeFilterFunction s for both Servlet- and WebFlux-based applications that both leverage this service.
There is a migration example available here,
https://github.com/jgrandja/spring-security-oauth-5-2-migrate
Related
I've got a spring boot application that is defining a RestTemplate bean as follows:
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder.build();
}
Also, pulling in spring-boot-starter-actuator and io.micrometer:micrometer-registry-prometheus.
When I use the injected RestTemplate as follows:
#Autowired
private RestTemplate restTemplate;
private String uriTemplate = "http://my.domain.com/bookstore-api/books";
public List<Book> getBooksByAuthor(String author) {
// create URI for "http://my.domain.com/bookstore-api/books?author={authorId}"
UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder
.fromUriString(uriTemplate)
.queryParam("author", author);
// make the GET
ResponseEntity<Book[]> responseEntity = restTemplate.getForEntity(uriComponentsBuilder.toUriString(),Book[].class);
// rest ommitted for brevity
}
When getBooksByAuthor("Tolkien") is called, we can then hit /metrics/prometheus and see the following:
http_client_requests_seconds_count{clientName="my.domain.com",method="GET",status="200",uri="/bookstore-api/books?author=Tolkien",} 2.0
http_client_requests_seconds_sum{clientName="my.domain.com",method="GET",status="200",uri="/bookstore-api/books?author=Tolkien",} 0.253227898
This would be fine, except that there are lots of authors out there, and eventually I will get the "too many tags" exception.
I would prefer to have the following (similar to how path variables get templated):
http_client_requests_seconds_count{clientName="my.domain.com",method="GET",status="200",uri="/bookstore-api/books?author={author}",} 2.0
http_client_requests_seconds_sum{clientName="my.domain.com",method="GET",status="200",uri="/bookstore-api/books?author={author}",} 0.253227898
Is this possible to achieve by modifying the way I use UriComponentsBuilder? The closest thing I've found is to define my own RestTemplateExchangeTagsProvider, and override the default to do some crazy regex replacement.
Just fixed same issue in SpringBoot 2.4.5 using:
responseEntity = restTemplate.exchange(
config.getDataUrl(),
HttpMethod.GET,
httpEntity,
new ParameterizedTypeReference<String>() {},
rowId);
where getDataUrl resolves to:
https://data-service-dev.apps.cloud.net/api/hbase/getData?rowId={rowId}
metrics:
http_client_requests_seconds_count{clientName="data-service-dev.apps.cloud.net",method="GET",outcome="SUCCESS",status="200",uri="/api/hbase/getData?rowId={rowId}",} 1.0
...
I have had a same problem. Maybe this information will help you.
In my case restTemplate.setUriTemplateHandler(handler) had overwritten annonymous
MetricsClientHttpRequestInterceptor#createUriTemplateHandler.
And the original url templates had not been stored into memory for prometheus.
DefaultUriBuilderFactory builderFactory = new DefaultUriBuilderFactory();
builderFactory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.NONE);
customizer.customize(restTemplate);
restTemplate.setUriTemplateHandler(handler);
So,
I changed order of the commands:
restTemplate.setUriTemplateHandler(handler);
customizer.customize(restTemplate);
Please check that there are no setting commands for restTemplate after MetricsClientHttpRequestInterceptor.customize(restTemplate).
I would like to implement a client which should simply send some rest calls with a OAuth2 token. Using spring-security-oauth it was pretty easy to use the OAuth2RestTemplate with a client-credentials flow. Today I saw most of those classes are deprecated in 2.4.0 and the recommendation is to use Spring Security 5. I've googled around and looked into the Migration Guide [1] but I've not understood what I've to do to perform some simple rest call which fetches a token with Spring Security 5. I think I'm even not sure what kind of libraries are needed. So what I'm basically is looking for is a way to provide a client-id, client-secret and a tokenendpoint programatically (not via properties) to some kind of rest template and send a request to a specific url.
--edit--
I found a way of using the web client without using the properties but rather using the ClientRegestration object. I'm not sure if that is a recommended way:
#Test
public void test() {
WebClient webClient = getWebClient();
ResponseSpec retrieve = webClient.get().uri("https://somepath")
.attributes(ServerOAuth2AuthorizedClientExchangeFilterFunction.clientRegistrationId(REG_ID)).retrieve();
Flux<String> result = retrieve.bodyToFlux(String.class); // flux makes no sense here, use Mono instead
Mono<List<String>> response = result.collectList();
List<String> block = response.block();
System.out.print(block);
System.out.print("debug");
}
public WebClient getWebClient() {
Builder clientRegestrationBuilder = ClientRegistration.withRegistrationId(REG_ID);
clientRegestrationBuilder.clientId(CLIENT_ID);
clientRegestrationBuilder.clientSecret(CLIENT_SECRET);
clientRegestrationBuilder.tokenUri(TOKEN_ENDPOINT);
clientRegestrationBuilder.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS);
ClientRegistration clientRegistration = clientRegestrationBuilder.build();
ReactiveClientRegistrationRepository repo = new InMemoryReactiveClientRegistrationRepository(clientRegistration);
ServerOAuth2AuthorizedClientExchangeFilterFunction oauth = new ServerOAuth2AuthorizedClientExchangeFilterFunction(repo,
new UnAuthenticatedServerOAuth2AuthorizedClientRepository());
return WebClient.builder().filter(oauth).build();
}
Regards
monti
[1] https://github.com/spring-projects/spring-security/wiki/OAuth-2.0-Migration-Guide
The following code is a unit test which shows how a ClientRegistration can be done programatically. In "real" spring scenario I guess the ClientRegistration should be provided as bean and finally injected as a list to a ReactiveClientRegistrationRepository...
public void test() {
WebClient webClient = getWebClient();
ResponseSpec retrieve = webClient.get().uri("https://somepath")
.attributes(ServerOAuth2AuthorizedClientExchangeFilterFunction.clientRegistrationId(REG_ID)).retrieve();
Flux<String> result = retrieve.bodyToFlux(String.class); // flux makes no sense here, use Mono instead
Mono<List<String>> response = result.collectList();
List<String> block = response.block();
System.out.print(block);
System.out.print("debug");
}
public WebClient getWebClient() {
Builder clientRegestrationBuilder = ClientRegistration.withRegistrationId(REG_ID);
clientRegestrationBuilder.clientId(CLIENT_ID);
clientRegestrationBuilder.clientSecret(CLIENT_SECRET);
clientRegestrationBuilder.tokenUri(TOKEN_ENDPOINT);
clientRegestrationBuilder.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS);
ClientRegistration clientRegistration = clientRegestrationBuilder.build();
ReactiveClientRegistrationRepository repo = new InMemoryReactiveClientRegistrationRepository(clientRegistration);
ServerOAuth2AuthorizedClientExchangeFilterFunction oauth = new ServerOAuth2AuthorizedClientExchangeFilterFunction(repo,
new UnAuthenticatedServerOAuth2AuthorizedClientRepository());
return WebClient.builder().filter(oauth).build();
}
I am trying to implement an Oauth2 Authorization Server with SpringBoot using this guide as a reference.
My keystore has a single key. I have successfully managed to create a JWToken (I can check it at jwt.io).
I have also a test Resource Server. When I try to access any endpoint I receive the following message:
{
"error": "invalid_token",
"error_description": "Invalid JWT/JWS: kid is a required JOSE Header"
}
The token really does not have a kid header but I can not figure out how to add it. I can only add data to its payload, using a TokenEnchancer. It also seems that I am not the first one with this issue.
Is there any way to add this header or, at least, ignore it at the resource server?
I've been working on an article that might help you out here:
https://www.baeldung.com/spring-security-oauth2-jws-jwk
So, to configure a Spring Security OAuth Authorization Server to add a JWT kid header, you can follow the steps of section 4.9:
create a new class extending the JwtAccessTokenConverter
In the constructor:
configure the parent class using the same approach you've been using
obtain a Signer object using the signing key you're using
override the encode method. The implementation will be the same as the parent one, with the only difference that you’ll also pass the custom headers when creating the String token
public class JwtCustomHeadersAccessTokenConverter extends JwtAccessTokenConverter {
private JsonParser objectMapper = JsonParserFactory.create();
final RsaSigner signer;
public JwtCustomHeadersAccessTokenConverter(KeyPair keyPair) {
super();
super.setKeyPair(keyPair);
this.signer = new RsaSigner((RSAPrivateKey) keyPair.getPrivate());
}
#Override
protected String encode(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
String content;
try {
content = this.objectMapper.formatMap(getAccessTokenConverter().convertAccessToken(accessToken, authentication));
} catch (Exception ex) {
throw new IllegalStateException("Cannot convert access token to JSON", ex);
}
Map<String, String> customHeaders = Collections.singletonMap("kid", "my_kid");
String token = JwtHelper.encode(content, this.signer, this.customHeaders)
.getEncoded();
return token;
}
}
Then, of course, create a bean using this converter:
#Bean
public JwtAccessTokenConverter accessTokenConverter(KeyPair keyPair) {
return new JwtCustomHeadersAccessTokenConverter(keyPair);
}
Here I used a KeyPair instance to obtain the signing key and configure the converter (based on the example of the article), but you might adapt that to your configuration.
In the article I also explain the relevant endpoints provided by the Spring Security OAuth Authentication Server.
Also, regarding #Ortomala Lokni's comment, I wouldn't expect Spring Security OAuth to add any new features at this point. As an alternative, you probably can wait to have a look at Spring Security's Authorization Server features, planned to be released in 5.3.0
I managed to solve it by changing the parameter used to identify the URL where the clients will retrieve the pubkey.
On application.properties, instead of:
security.oauth2.resource.jwk.key-set-uri=http://{auth_server}/.well-known/jwks.json
I used:
security.oauth2.resource.jwt.key-uri=http://{auth_server}/oauth/token_key
If I understood correctly, the key-set-uri config points to an endpoint that presents a set of keys and there is the need for a kid. On the other side key-uri config points to an endpoint with a single key.
I have implemented Oauth2 client in spring boot
public RestTemplate oAuthRestTemplate() {
ClientCredentialsResourceDetails resourceDetails = new ClientCredentialsResourceDetails();
resourceDetails.setId("1");
resourceDetails.setClientId(clientId);
resourceDetails.setClientSecret(clientSecret);
resourceDetails.setAccessTokenUri(accessTokenUrl);
resourceDetails.setTokenName("accessToken");
resourceDetails.setClientAuthenticationScheme(AuthenticationScheme.form);
OAuth2RestTemplate restTemplate = new OAuth2RestTemplate(resourceDetails, new DefaultOAuth2ClientContext());
return restTemplate;
}
We I run the code, request body for token is as
client_id & client_secret (by default)
Can we send is custome manner.
like I want to send it as clientId & clientSecret.
Note: Class is annotated with #EnableOAuth2Client
The names 'client_id' and 'client_secret' are specified by OAuth 2
So there is normally no need to change these.
However, Spring uses org.springframework.security.oauth2.client.token.auth.DefaultClientAuthenticationHandler
to post the client_id and client_secret. There you can see the field names are hardcoded :
form.set("client_id", resource.getClientId());
if (StringUtils.hasText(clientSecret)) {
form.set("client_secret", clientSecret);
}
You could try to subclass DefaultClientAuthenticationHandler, and use your own field names. But as it is created with 'new' in OAuth2AccessTokenSupport, you would need to subclass this also, and so on......
This can be tricky, a better way may be to add a
org.springframework.http.client.ClientHttpRequestInterceptor
at the org.springframework.security.oauth2.client.OAuth2RestTemplate
Create a class that implements ClientHttpRequestInterceptor, in its method
org.springframework.http.client.ClientHttpRequestInterceptor#intercept
you need to create a new request using the provided body of type byte[].
Convert it to a String, replace the field names, create a new request using it and proceed as described in the javadoc of the intercept() method.
To register the interceptor, create a spring bean of type OAuth2RestTemplate and register the interceptor there like
#Bean
OAuth2RestTemplate oAuth2RestTemplate(){
OAuth2RestTemplate template=new OAuth2RestTemplate;
ClientHttpRequestInterceptor interceptor= new ... //create your interceptor here
template.setInterceptors(Arrays.asList(interceptor));
return template;
}
On my current project I have an app that has a small graphical piece that users authenticate using SSO, and a portion that is purely API where users authenticate using an Authorization header.
For example:
/ping-other-service is accessed using SSO.
/api/ping-other-service is accessed using a bearer token
Being all cloud native our app communicates with other services that uses the same SSO provider using JWT tokens (UAA), so I figured we'd use OAuth2RestTemplate since according to the documentation it can magically insert the authentication credentials. It does do that for all endpoints that are authenticated using SSO. But when we use an endpoint that is authed through bearer token it doesn't populate the rest template.
My understanding from the documentation is that #EnableOAuth2Client will only extract the token from a SSO login, not auth header?
What I'm seeing
Failed request and what it does:
curl -H "Authorization: Bearer <token>" http://localhost/api/ping-other-service
Internally uses restTemplate to call http://some-other-service/ping which responds 401
Successful request and what it does:
Chrome http://localhost/ping-other-service
Internally uses restTemplate to call http://some-other-service/ping which responds 200
How we worked around it
To work around this I ended up creating the following monstrosity which will extract the token from the OAuth2ClientContext if it isn't available from an authorization header.
#PostMapping(path = "/ping-other-service")
public ResponseEntity ping(#PathVariable String caseId, HttpServletRequest request, RestTemplate restTemplate) {
try {
restTemplate.postForEntity(adapterUrl + "/webhook/ping", getRequest(request), Map.class);
} catch (HttpClientErrorException e) {
e.printStackTrace();
return new ResponseEntity(HttpStatus.SERVICE_UNAVAILABLE);
}
return new ResponseEntity(HttpStatus.OK);
}
private HttpEntity<?> getRequest(HttpServletRequest request) {
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", "Bearer " + getRequestToken(request));
return new HttpEntity<>(null, headers);
}
private String getRequestToken(HttpServletRequest request) {
Authentication token = new BearerTokenExtractor().extract(request);
if (token != null) {
return (String) token.getPrincipal();
} else {
OAuth2AccessToken accessToken = oAuth2ClientContext.getAccessToken();
if (accessToken != null) {
return accessToken.getValue();
}
}
throw new ResourceNotFound("No valid access token found");
}
In the /api/** resources there is an incoming token, but because you are using JWT the resource server can authenticate without calling out to the auth server, so there is no OAuth2RestTemplate just sitting around waiting for you to re-use the context in the token relay (if you were using UserInfoTokenServices there would be one). You can create one though quite easily, and pull the incoming token out of the SecurityContext. Example:
#Autowired
private OAuth2ProtectedResourceDetails resource;
private OAuth2RestTemplate tokenRelayTemplate(Principal principal) {
OAuth2Authentication authentication = (OAuth2Authentication) principal;
OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) authentication.getDetails();
details.getTokenValue();
OAuth2ClientContext context = new DefaultOAuth2ClientContext(new DefaultOAuth2AccessToken(details.getTokenValue()));
return new OAuth2RestTemplate(resource, context);
}
You could probably turn that method into #Bean (in #Scope("request")) and inject the template with a #Qualifier if you wanted.
There's some autoconfiguration and a utility class to help with this pattern in Spring Cloud Security, e.g: https://github.com/spring-cloud/spring-cloud-security/blob/master/spring-cloud-security/src/main/java/org/springframework/cloud/security/oauth2/client/AccessTokenContextRelay.java
I came across this problem when developing a Spring resource server, and I needed to pass the OAuth2 token from a request to the restTemplate for a call to a downstream resource server. Both resource servers use the same auth server, and I found Dave's link helpful but I had to dig a bit to find out how to implement this. I ended up finding the documentation here, and it turn's out the implemetation was very simple. I was using #EnableOAuth2Client, so I had to create the restTemplate bean with the injected OAuth2ClientContext and create the appropriate resource details. In my case it was ClientCredentialsResourceDetails. Thanks for all great work Dave!
#Bean
public OAuth2RestOperations restTemplate (OAuth2ClientContext context) {
ClientCredentialsResourceDetails details = new ClientCredentialsResourceDetails();
// Configure the details here
return new OAuth2RestTemplate(details, context)
}
#Dave Syer
My UAA service is also an oauth2 client, which needs to relay JWT tokens coming in from Zuul. When configuring the oauth2 client the following way
#Configuration
#EnableOAuth2Client
#RibbonClient(name = "downstream")
public class OAuthClientConfiguration {
#Bean
public OAuth2RestTemplate restTemplate(OAuth2ProtectedResourceDetails resource, OAuth2ClientContext context) {
return new OAuth2RestTemplate(resource, context);
}
}
I do get a 401 response from the downstream service as my access token has a very short validity and the AccessTokenContextRelay does not update an incoming access token (Zuul does renew expired access tokens by the refresh token).
The OAuth2RestTemplate#getAccessToken will never acquire a new access token as the isExpired on the access token stored by the AccessTokenContextRelay drops the validity and refresh token information.
How can this by solved?