Spring WebClient and shared client credential token for all requests - spring

I tried to use Spring WebClient and Spring Security OAuth2 Client for communication between two applications. I need to use OAuth2 Client Credentials grant type and use the same credentials and the same token for all requests between these applications. In the OAuth2 terminology resource owner is an application A itself and resource server is an application B (it is Keycloak Admin API). I use Spring Boot auto configuration from application.yaml and servlet environment. So I implemented it like this:
WebClient buildWebClient(ClientRegistrationRepository clientRegistrationRepository,
OAuth2AuthorizedClientRepository authorizedClientRepository, String clientName) {
final ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client = new ServletOAuth2AuthorizedClientExchangeFilterFunction(
clientRegistrationRepository, authorizedClientRepository);
oauth2Client.setDefaultClientRegistrationId(clientName);
return WebClient.builder()
.apply(oauth2Client.oauth2Configuration())
.build();
}
I found that I have an issue and invalid tokens (I use Keycloak and if my Keycloak is restarted then all old tokens are invalid) are never removed (you can see it in issue #9477) so I started debuging whole process of authorization in ServletOAuth2AuthorizedClientExchangeFilterFunction. I found that tokens are saved per user of the application A. It means that every user of the application A has its own token for authorization to the application B. But this is not my intended behavior I want to use only one token in the application A for consuming a resource from the application B.
I think that I found two solutions but both of them are not optimal.
Solution 1.:
Add another default request after ServletOAuth2AuthorizedClientExchangeFilterFunction which replaces user Authentication in attributes by an application Authentication. Problem is that it dependes on an internal implemention of ServletOAuth2AuthorizedClientExchangeFilterFunction which use a private attribute AUTHENTICATION_ATTR_NAME = Authentication.class.getName();.
return WebClient.builder()
.apply(oauth2Client.oauth2Configuration())
.defaultRequest(spec -> spec.attributes(attr ->
attr.put(AUTHENTICATION_ATTR_NAME, new CustomApplicaitonAuthentication())
)
.build();
Solution 2.:
Use a custom implementation of OAuth2AuthorizedClientRepository instead of default AuthenticatedPrincipalOAuth2AuthorizedClientRepository (which I have to do anyway because of the issue with removing invalid tokens) where I ignore user's Authentication and I use a custom application Authentication.
#Override
public <T extends OAuth2AuthorizedClient> T loadAuthorizedClient(String clientRegistrationId, Authentication principal,
HttpServletRequest request) {
return this.authorizedClientService.loadAuthorizedClient(clientRegistrationId, new CustomApplicaitonAuthentication());
}
#Override
public void saveAuthorizedClient(OAuth2AuthorizedClient authorizedClient, Authentication principal,
HttpServletRequest request, HttpServletResponse response) {
this.authorizedClientService.saveAuthorizedClient(authorizedClient, new CustomApplicaitonAuthentication());
}
But I think that both my solutions are not optimal. Is there another way how to do it more straightforward?

I found solution because I needed to run it without a servlet request (messaging, scheduler etc.). Therefore I needed to update it and use the AuthorizedClientServiceOAuth2AuthorizedClientManager instead of the default DefaultOAuth2AuthorizedClientManager. Then I reallized that it does the same thing like I need.
#Bean
WebClient buildWebClient(
OAuth2AuthorizedClientManager oAuth2AuthorizedClientManager,
OAuth2AuthorizationFailureHandler oAuth2AuthorizationFailureHandler,
String clientName) {
final ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client = new ServletOAuth2AuthorizedClientExchangeFilterFunction(
oAuth2AuthorizedClientManager);
oauth2Client.setDefaultClientRegistrationId(clientName);
oauth2Client.setAuthorizationFailureHandler(oAuth2AuthorizationFailureHandler);
return WebClient.builder()
.apply(oauth2Client.oauth2Configuration())
.build();
}
#Bean
public OAuth2AuthorizedClientManager authorizedClientManager
(ClientRegistrationRepository clients, OAuth2AuthorizedClientService service, OAuth2AuthorizationFailureHandler authorizationFailureHandler) {
AuthorizedClientServiceOAuth2AuthorizedClientManager manager =
new AuthorizedClientServiceOAuth2AuthorizedClientManager(clients, service);
manager.setAuthorizationFailureHandler(authorizationFailureHandler);
return manager;
}
#Bean
public OAuth2AuthorizationFailureHandler resourceServerAuthorizationFailureHandler(
OAuth2AuthorizedClientService authorizedClientService) {
return new RemoveAuthorizedClientOAuth2AuthorizationFailureHandler(
(clientRegistrationId, principal, attributes) ->
authorizedClientService.removeAuthorizedClient(clientRegistrationId, principal.getName()));
}
We had to add a custom OAuth2AuthorizationFailureHandler because if you do not use this constructor ServletOAuth2AuthorizedClientExchangeFilterFunction(ClientRegistrationRepository clientRegistrationRepository, OAuth2AuthorizedClientRepository authorizedClientRepository) and you use a custom OAuth2AuthorizedClientManager then it is not configured and you have to do it manually.

Related

Spring Boot Reactive WebClient "serverWebExchange must be null" when Spring Security OAuth is in use

I would like to use a WebClient from a Spring Boot WebFlux app that is set up with Spring Security OAuth 2 Client Credentials.
But, I get: java.lang.IllegalArgumentException: serverWebExchange must be null
The code is here: https://github.com/mparaz/spring-apigee-client
When I disable Spring Security by removing it from the pom.xml, it works properly.
When I keep using Spring Security, but instead of returning the webClient() chain result to the controller, and just prints it out, it also works.
It looks like the Reactive client and server don't work together when Spring Security is used. How could I get them running together?
For me the problem was following
https://docs.spring.io/spring-security/site/docs/5.5.x/reference/html5/#oauth2Client-authorized-manager-provider
The DefaultOAuth2AuthorizedClientManager is designed to be used within the context of a HttpServletRequest. When operating outside of a HttpServletRequest context, use AuthorizedClientServiceOAuth2AuthorizedClientManager instead.
Comment on the following link worked for me
https://www.gitmemory.com/issue/spring-projects/spring-security/8444/621567261
Another link
https://github.com/spring-projects/spring-security/issues/8230
Comment
DefaultReactiveOAuth2AuthorizedClientManager is intended to be used within a request context.
Given that you're seeing serverWebExchange cannot be null, you must be operating outside of a request context, which in case you should use AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager instead.
NOTE: Change the ServerOAuth2AuthorizedClientRepository parameter to ReactiveOAuth2AuthorizedClientService.
Actual code
#Bean
fun serverOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrations: List<ClientRegistration>)
: ServerOAuth2AuthorizedClientExchangeFilterFunction {
val clientRegistrationRepository = InMemoryReactiveClientRegistrationRepository(clientRegistrations)
val authorizedClientService = InMemoryReactiveOAuth2AuthorizedClientService(clientRegistrationRepository)
val oAuth2AuthorizedClientManager = AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(
clientRegistrationRepository,
authorizedClientService
)
val filterFunction = ServerOAuth2AuthorizedClientExchangeFilterFunction(oAuth2AuthorizedClientManager)
filterFunction.setDefaultClientRegistrationId(clientId)
return filterFunction
}
It seems that if you use an UnAuthenticatedServerOAuth2AuthorizedClientRepository it will propagate the webExchange from the source request going into to your #RestController into the WebClient you are using to invoke the other service causing the java.lang.IllegalArgumentException: serverWebExchange must be null
To fix this, use the autowired implementation of the ServerOAuth2AuthorizedClientRepository (this happens to be AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository)
#Bean
#LoadBalanced
public WebClient.Builder loadBalancedWebClientBuilder(ReactiveClientRegistrationRepository clientRegistrations,
ObjectMapper objectMapper,
ServerOAuth2AuthorizedClientRepository clientRepository) {
ServerOAuth2AuthorizedClientExchangeFilterFunction oauth2ClientFilter = new ServerOAuth2AuthorizedClientExchangeFilterFunction(
clientRegistrations,
clientRepository);
oauth2ClientFilter.setDefaultClientRegistrationId("apigee");
WebClient.Builder builder = WebClient.builder();
builder.defaultHeader("Content-Type", MediaType.APPLICATION_JSON.toString());
builder.defaultHeader("Accept", MediaType.APPLICATION_JSON.toString());
builder.filter(oauth2ClientFilter);
return builder;
}

Main concepts of spring security with JWT tokens (Spring boot with REST controllers)

Introduction:
I have just started using spring boot. For understanding how it works I have tried to convert my existing project (spring MVC, JSP in frontend) to spring boot approach with REST-controller and AngularJS in frontend.
Facing problem:
During migration I have faced to big problem with security. As I understood the best way for having good security layer now is working with JWT tokens and supporting oauth2, on which there are a lot of posts/tutorials which give different information even about basics of the security layer architecture.
So the question is:
Could someone point out full list of security-layer parts/classes which are needed for having basic (but not hello world) security features for spring boot app with REST controllers. Please don't suggest to use stormpath: I want to implement it myself to get better understanding.
Reasoning of asking this big question here:
I have done my own investigation on this topic, but I thought that most of the links which I have checked contain a lot of bad practices, so possible incorrect architecture of security layer. so I really would like to know some kind of good practice of designing architecture of security layer.
Details on needed features:
I have standard list of features which I want to support.
oauth2 support (but also to have possibility to authenticate without it)
register request (creation of jwt token and returning to client)
login request (acquiring jwt token if user was registered)
logout request (releasing jwt token)
token timeout
multiple roles
business rest controllers which checks for authentication and authorization (could you please give an example portion of code)
business rest controllers which doesn't require security
basic filtering http urls (like excluding "statics" from allowed url addresses)
Current layers of the project:
Below are some additional information about my current project structure:
Currently I have implemented the following modules:
controller: Currently MVC controllers, but I am going to convert them to REST
dto: Possibly will be changed a little bit, because of REST approach
model: Will stay unchanged after conversation
exception: For business logic
repository: Will stay unchanged after conversation
service: Possibly will be changed a little bit, because of micro-services
validator: Will stay unchanged after conversation
other business logic modules
If I understood correctly I will need to add two additional layers here:
configuration: I have already converted some xml configurators to java-configs, but haven't touched security configurators
security: I guest here will be placed authentication/authorization managers/tools. One of the goals of this question is to understand what exactly to place here.
app class with main method in the root package (relative root)
You can start by creating 3 projects.
Auth Server: This will take care of authenticating clients and users, issuing token, revoking token etc.
Rest API: All rest controllers, business logic, persistence layer etc.
Front-end: Angular JS, HTML, CSS etc.
Read about OAuth2 grant types.
We use password authorization grant type when authorization server and client is developed by same organization, or when there is a high degree of trust between the resource owner and the client.
Following are the essential classes you'd need for OAuth2 implementation:
A class which extends AuthorizationServerConfigurerAdapter to configure authorization server.
Here you can configure endpoints like userDetailsService (custom class to load user data by username from database), tokenStore (to store tokens in database and perform operations on it), clientDetailsService (load client details from database; your Rest API project could be client).
#Override
public void configure(final AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager);
endpoints.userDetailsService(userDetailsService);
endpoints.tokenStore(tokenStore);
endpoints.setClientDetailsService(clientDetailsService);
endpoints.accessTokenConverter(accessTokenConverter);
}
#Override
public void configure(final AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
//The expression below easies access to /oauth/check_token endpoint from the default denyAll to isAuthenticated.
oauthServer.checkTokenAccess("isAuthenticated()");
oauthServer.allowFormAuthenticationForClients();
oauthServer.passwordEncoder(passwordEncoder);
}
#Override
public void configure(final ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(clientDetailsService);
}
A class which extends ResourceServerConfigurerAdapter. Here you can configure security configuration for the resource server. Resources would be Rest controllers defined in Auth Servers (like controllers for performing CRUD operation on a user object, endpoint to revoke token; controllers which need to be in Auth Server).
#Override
public void configure(final HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().fullyAuthenticated(); //To restrict all http requests.
/*http.authorizeRequests().antMatchers("/users/**").permitAll(); //Notice ant matcher here, this tells endpoints which do not require authentication. Lots of http configuration options (like applying filters, cors, csrf etc.) are available here. Please explore*/
}
Check out TokenStore default implementation classes (like JdbcTokenStore, JwtTokenStore). If you'd like to use NoSQL db like Cassandra then provide custom TokenStore implementation.
Following is the sample code snippet for custom Token Store used for Cassandra:
#Override
public void storeAccessToken(final OAuth2AccessToken token, final OAuth2Authentication authentication) {
String refreshToken = null;
if (token.getRefreshToken() != null) {
refreshToken = token.getRefreshToken().getValue();
}
if (readAccessToken(token.getValue()) != null) {
removeAccessToken(token.getValue());
}
final AccessTokenBuilder accessTokenBuilder = new AccessTokenBuilder();
accessTokenRepository.save(accessTokenBuilder
.withAuthenticationId(authenticationKeyGenerator.extractKey(authentication))
.withTokenId(extractTokenKey(token.getValue()))
.withTokenBody(ByteBuffer.wrap(serializeAccessToken(token)))
.withUsername(authentication.getName())
.withClientId(authentication.getOAuth2Request().getClientId())
.withAuthentication(ByteBuffer.wrap(serializeAuthentication(authentication)))
.withRefreshTokenId(extractTokenKey(refreshToken))
.build());
}
#Override
public void storeRefreshToken(final OAuth2RefreshToken refreshToken, final OAuth2Authentication authentication) {
final RefreshTokenBuilder refreshTokenBuilder = new RefreshTokenBuilder();
refreshTokenRepository.save(refreshTokenBuilder
.withTokenId(extractTokenKey(refreshToken.getValue()))
.withTokenBody(ByteBuffer.wrap(serializeRefreshToken(refreshToken)))
.withAuthentication(ByteBuffer.wrap(serializeAuthentication(authentication)))
.build());
}
#Override
public OAuth2Authentication readAuthentication(final OAuth2AccessToken token) {
return readAuthentication(token.getValue());
}
#Override
public OAuth2Authentication readAuthentication(final String token) {
OAuth2Authentication authentication = null;
try {
final AccessToken authAccessToken = accessTokenRepository.findByTokenId(extractTokenKey(token));
authentication = deserializeAuthentication(authAccessToken.getAuthentication().array());
} catch (final IllegalArgumentException e) {
removeAccessToken(token);
}
return authentication;
}
#Override
public OAuth2AccessToken readAccessToken(final String tokenValue) {
final AccessToken accessToken = accessTokenRepository.findByTokenId(extractTokenKey(tokenValue));
return accessToken != null ? deserializeAccessToken(accessToken.getTokenBody().array()) : null;
}
#Override
public OAuth2RefreshToken readRefreshToken(final String tokenValue) {
final RefreshToken refreshToken = refreshTokenRepository.findOne(extractTokenKey(tokenValue));
return refreshToken != null ? deserializeRefreshToken(refreshToken.getTokenBody().array()) : null;
}
#Override
public OAuth2Authentication readAuthenticationForRefreshToken(final OAuth2RefreshToken token) {
return readAuthenticationForRefreshToken(token.getValue());
}
OAuth2Authentication readAuthenticationForRefreshToken(final String tokenValue) {
final RefreshToken refreshToken = refreshTokenRepository.findOne(extractTokenKey(tokenValue));
return refreshToken != null ? deserializeAuthentication(refreshToken.getAuthentication().array()) : null;
}
#Override
public OAuth2AccessToken getAccessToken(final OAuth2Authentication authentication) {
OAuth2AccessToken oAuth2AccessToken = null;
final String key = authenticationKeyGenerator.extractKey(authentication);
final AccessToken accessToken = accessTokenRepository.findOne(key);
if (accessToken != null) {
oAuth2AccessToken = deserializeAccessToken(accessToken.getTokenBody().array());
if (oAuth2AccessToken != null && !key.equals(authenticationKeyGenerator.extractKey(readAuthentication(oAuth2AccessToken.getValue())))) {
removeAccessToken(oAuth2AccessToken.getValue());
storeAccessToken(oAuth2AccessToken, authentication);
}
}
return oAuth2AccessToken;
}
You'd need to declare repository interfaces for db operations. Interfaces which extends CrudRepository. For most of the DB operations we don't need to provide implementation, it is handled by Spring. For Cassandra implementation is in SimpleCassandraRepository class. Sample code for Access Token:
public interface AccessTokenRepository extends CrudRepository<AccessToken, String> {
#Query("SELECT * FROM auth_service.oauth_access_token WHERE token_id = :tokenId ALLOW FILTERING")
AccessToken findByTokenId(#Param("tokenId") String tokenId);
}
Sample code for ClientDetails
public interface ClientDetailsRepository extends CrudRepository<ClientDetails, String> {
}
Please note, we don't need to provide implementation for these interfaces. Regular CRUD queries are already implemented and taken care by Spring.
public interface RefreshTokenRepository extends CrudRepository<RefreshToken, String> {
}
Rest API project
Controllers declared here would get called when request received from frontend (AJAX request from javascript). All business logic and persistence layer would go here.
Here you can think about creating a module, a gateway, which talks to Auth Server. This gateway would be between your Rest API and Auth Server.
You can use RestTemplate to call remote Rest service.
If you need that not any Rest API project can make remote calls to Auth Server, then user client_credentials as well along with password grant type. And, use OAuth2RestTemplate instead of RestTemplate. Sample code:
<bean id="oAuth2RestTemplate" class="org.springframework.security.oauth2.client.OAuth2RestTemplate">
<constructor-arg ref="clientCredentialsResourceDetails"/>
<constructor-arg ref="defaultOAuth2ClientContext"/>
<property name="requestFactory" ref="httpComponentsClientHttpRequestFactory"/>
</bean>
<bean id="httpComponentsClientHttpRequestFactory" class="org.springframework.http.client.HttpComponentsClientHttpRequestFactory">
<constructor-arg ref="selfSignedHttpsClientFactory"/>
</bean>
<bean id="clientCredentialsResourceDetails" class="org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails">
<property name="accessTokenUri" value="${authentication.service.client.token.url:https://localhost:8443/oauth/token}"/>
<property name="clientId" value="${authentication.service.client.id:testClient}"/>
<property name="clientSecret" value="${authentication.service.client.secret:password}"/>
</bean>
<bean id="defaultOAuth2ClientContext" class="org.springframework.security.oauth2.client.DefaultOAuth2ClientContext"/>
I hope this was helpful.
Not sure if you have seen this, but here is a nice article:
https://www.toptal.com/java/rest-security-with-jwt-spring-security-and-java
. And a project on github, more or less based on that article:
https://github.com/szerhusenBC/jwt-spring-security-demo

Spring Boot web service client authentication

My goal is to call web service, which is require authentification (when I opne it's wsdl in my browser, browser asks me login+password).
As a base, I use the sample from this tutorial.
And now I have to add authentification configurations.
Accoding to the documentation something like configuring WebServiceTemplate bean may help.
But with Spring Boot there are no applicationContext.xml or any other configuration xml's in a project.
So, how to configure WebServiceTemplate using Spring Boot, or what else can solve such task?
In Spring Boot you are able to configure your beans with the #Bean annotation. You can use configuration classes for different beans. In those classes you need the #Configuaration annotation.
This tutorial describes the "second part" of the Spring tutorial. The main things of provided tutorial is: (based on the Spring tutorial)
The problem
The SOAP webservice I consume requires basic http authentication, so I
need to add authentication header to the request.
Without authentication
First of all you need to have implemented a request without the
authentication like in the tutorial on the spring.io. Then I will
modify the http request with the authentication header.
Get the http request in custom WebServiceMessageSender
The raw http connection is accessible in the WeatherConfiguration
class. There in the weatherClient you can set the message sender in
the WebServiceTemplate. The message sender has access to the raw http
connection. So now it’s time to extend the
HttpUrlConnectionMessageSender and write custom implementation of it
that will add the authentication header to the request. My custom
sender is as follows:
public class WebServiceMessageSenderWithAuth extends HttpUrlConnectionMessageSender{
#Override
protected void prepareConnection(HttpURLConnection connection)
throws IOException {
BASE64Encoder enc = new sun.misc.BASE64Encoder();
String userpassword = "yourLogin:yourPassword";
String encodedAuthorization = enc.encode( userpassword.getBytes() );
connection.setRequestProperty("Authorization", "Basic " + encodedAuthorization);
super.prepareConnection(connection);
}
#Bean
public WeatherClient weatherClient(Jaxb2Marshaller marshaller){
WebServiceTemplate template = client.getWebServiceTemplate();
template.setMessageSender(new WebServiceMessageSenderWithAuth());
return client;
}
I faced the same issue and solved by following.
Basic idea was to create CredentialsProvider with basic username and password along with AuthScope.ANY:
#Bean
public WebServiceMessageSender showReqMessageSender(#Value("${ws.username}") String username,
#Value("${ws.passowrd}") String password) throws Exception {
final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(username, password));
return new HttpComponentsMessageSender(
HttpClientBuilder.create().setDefaultCredentialsProvider(credentialsProvider)
.addInterceptorFirst(new RemoveSoapHeadersInterceptor()).build());
}
Just for further info, this message sender bean is further used (set using class extedning WebServiceGatewaySupport)
void org.springframework.ws.client.core.support.WebServiceGatewaySupport.setMessageSender(WebServiceMessageSender messageSender)
Another walk-around is to add an interceptor and add the requestHeader within the handleRequest() method from which the HttpUrlConnection can be easily derived from the TransportContextHolder;
here is the code of the interceptor class:
public class SecurityInterceptor implements ClientInterceptor {
#Override
public boolean handleRequest(MessageContext messageContext) throws WebServiceClientException {
TransportContext context = TransportContextHolder.getTransportContext();
HttpUrlConnection connection = (HttpUrlConnection) context.getConnection();
try {
connection.addRequestHeader("Authorization","Basic VVNFUk5BTUU6cGFzc3dvcmQ=");
} catch (IOException e) {
log.error(e.getMessage());
}
return true;
}
//TODO:: other methods and constructor..
}
and of course add the interceptor to the WebTemplate:
WebServiceTemplate webServiceTemplate = new WebServiceTemplate(marshaller);
ClientInterceptor[] interceptors = new ClientInterceptor[]{new SecurityInterceptor()};
webServiceTemplate.setInterceptors(interceptors);
webServiceTemplate.marshalSendAndReceive(uriWebService, request)

Unable to refresh OAuth token with custom AuthenticationManager and JWT tokens enabled

We are facing a problem with Spring Security OAuth2 (v 2.0.8). The app is a Spring Boot application (v 1.2.6) with a custom authentication manager and it uses JWT tokens instead of default random value tokens. We are also using Spring Cloud Angel.SR4 and will upgrade to Brixton eventually but at the moment we "cannot" move to Spring Boot 1.3.
The application uses the default Spring Security configuration parameters to configure security for actuator endpoints:
security.user.name:user
security.user.password:password
This is the relevant part of the OAuth2 configuration:
#Configuration
#EnableAuthorizationServer
protected static class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
private AuthenticationManager oauthUserAuthenticationManager() {
Collection<UserDetails> users = new LinkedList<>();
users.add(new User("john", "doe", Arrays.asList(new SimpleGrantedAuthority("USER"))));
users.add(new User("jane", "doe", Arrays.asList(new SimpleGrantedAuthority("USER"), new SimpleGrantedAuthority("ADMIN"))));
UserDetailsManager manager = new InMemoryUserDetailsManager(users);
DaoAuthenticationProvider p = new DaoAuthenticationProvider();
p.setUserDetailsService(manager);
return new ProviderManager(Arrays.asList(p));
}
#Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
Resource fsr = resourceLoader.getResource(keystore);
KeyPair keyPair =
new KeyStoreKeyFactory(fsr, keystorePassword.toCharArray())
.getKeyPair(keyPairAlias, keyPairPassword.toCharArray());
converter.setKeyPair(keyPair);
return converter;
}
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.authenticationManager(oauthUserAuthenticationManager())
.accessTokenConverter(jwtAccessTokenConverter());
}
}
The application works fine when using password grant (grant_type=password) and tokens are successfully granted using john/doe or jane/doe. However, when trying to use the refresh token grant (grant_type=refresh_token), an exception is thrown: UsernameNotFoundException: john.
From debugging I have seen that the error originates in DefaultTokenServices.refreshAccessToken() (line 150) when the authenticationManager is used to verify that the user still exists:
user = authenticationManager.authenticate(user);
The debugger also reveals that an authentication with username user is accepted by the authenticationManager, so it looks as if the tokenServices uses the default authentication manager and the password token granter (ResourceOwnerPasswordTokenGranter) uses the custom authentication manager that was configured by the AuthorizationServerEndpointsConfigurer in our configuration.
We believe that what we need to do is to configure the same authentication manager in both places, but how?

How to overwrite Spring Cloud OAuth2 client autoconfiguration?

We want to setup a microservice which provides a REST API so it is configured as a OAuth2 resource server. This service should also act as a OAuth2 client with the client credential grant. Here is the configuration:
spring.oauth2.client.id=clientCredentialsResource
spring.oauth2.client.accessTokenUri=http://localhost:9003/oauth/token
spring.oauth2.client.userAuthorizationUri=http://localhost:9003/oauth/authorize
spring.oauth2.client.grantType=client_credentials
spring.oauth2.client.clientId=<service-id>
spring.oauth2.client.clientSecret=<service-pw>
The resource server part works fine. For the client part we want to use Feign, Ribbon and Eureka:
#FeignClient("user")
public interface UserClient
{
#RequestMapping( method = RequestMethod.GET, value = "/user/{uid}")
Map<String, String> getUser(#PathVariable("uid") String uid);
}
Based on the gist in issue https://github.com/spring-cloud/spring-cloud-security/issues/56 I created a feign request intercepter which sets the access token from the autowired OAuth2RestOperations template in the feign request header
#Autowired
private OAuth2RestOperations restTemplate;
template.header(headerName, String.format("%s %s", tokenTypeName, restTemplate.getAccessToken().toString()));
But this gives me the error on calling the user service:
error="access_denied", error_description="Unable to obtain a new access token for resource 'clientCredentialsResource'. The provider manager is not configured to support it.
As I can see the OAuth2ClientAutoConfiguration creates always an instance of AuthorizationCodeResourceDetails for an web application but not the required ClientCredentialsResourceDetails which is only used for non-web applications. In the end the no access token privider is responsible for the resource details and the call failed in
AccessTokenProviderChain.obtainNewAccessTokenInternal(AccessTokenProviderChain.java:146)
I tried to overwrite the auto configuration but failed. Can somebody please give me a hint how to do it?
To switch off this piece of autoconfiguration you can set spring.oauth2.client.clientId= (empty), (per the source code), otherwise you have to "exclude" it in the #EnableAutoConfiguration. If you do that you can just set up your own OAuth2RestTemplate and fill in the "real" client ID from your own configuration, e.g.
#Configuration
#EnableOAuth2Client
public class MyConfiguration {
#Value("myClientId")
String myClientId;
#Bean
#ConfigurationProperties("spring.oauth2.client")
#Primary
public ClientCredentialsResourceDetails oauth2RemoteResource() {
ClientCredentialsResourceDetails details = new ClientCredentialsResourceDetails();
details.setClientId(myClientId);
return details;
}
#Bean
public OAuth2ClientContext oauth2ClientContext() {
return new DefaultOAuth2ClientContext(new DefaultAccessTokenRequest());
}
#Bean
#Primary
public OAuth2RestTemplate oauth2RestTemplate(
OAuth2ClientContext oauth2ClientContext,
OAuth2ProtectedResourceDetails details) {
OAuth2RestTemplate template = new OAuth2RestTemplate(details,
oauth2ClientContext);
return template;
}
}

Resources