I am implementing a client that authenticates with OAuth2 against WSO2 and I have serious trouble refreshing the access token, getting 401 UNAUTHORIZED. While I have already found out, what the Spring OAuth2 code does, I do not know why its behavior was changed in 2.2.1.RELEASE and to me it seems plain wrong. Actually using 2.0.14.RELEASE works.
Before I am going to show to you, what I have done and what I have already found out, let me formulate my question:
How am I supposed to realize an OAuth2 client with automatic token refresh with client credentials instead of user credentials?
So here is, what I have implemented so far. The client configures an OAuth2RestTemplate
with ResourceOwnerPasswordResourceDetails
with isClientOnly
flag true
, as there are no user sessions. The client session can successfully be established and an access token and a refresh token are set.
@Bean
protected OAuth2ProtectedResourceDetails resource() {
ResourceOwnerPasswordResourceDetails resource = new ResourceOwnerPasswordResourceDetails() {
@Override
public boolean isClientOnly() {
return true;
}
};
List<String> scopes = new ArrayList<>(2);
scopes.add("write");
scopes.add("read");
resource.setScope(scopes);
resource.setGrantType("password");
resource.setAccessTokenUri(TOKEN_URL);
resource.setClientId(MY_CLIENT_ID);
resource.setClientSecret(MY_CLIENT_SECRET);
resource.setUsername(MY_SERVICE_USER);
resource.setPassword(MY_SERVICE_USER_PW);
return resource;
}
@Bean
public OAuth2RestOperations restTemplate() {
AccessTokenRequest atr = new DefaultAccessTokenRequest();
OAuth2RestTemplate template = new OAuth2RestTemplateWithBasicAuth(resource(), new DefaultOAuth2ClientContext(atr));
List<ClientHttpRequestInterceptor> interceptors = new ArrayList<ClientHttpRequestInterceptor>();
interceptors.add(new LoggingRequestInterceptor());
template.setInterceptors(interceptors);
template.setRetryBadAccessTokens(true);
return template;
}
So far so good. I have verified that this basically works.
But as soon as the access token expires I frequently run into 401 errors, because the token refresh is not executed. Instead, an ordinary authentication request is carried out, but using the client key and secret instead of user/password. To cut a long story short, I have debugged my way through spring-security-oauth2 into AccessTokenProviderChain#obtainAccessToken
and found out, that whether a token refresh request is executed is decided upon in the following bit of code. See on Github
if (resource.isClientOnly() || (auth != null && auth.isAuthenticated())) { // P1
existingToken = request.getExistingToken();
if (existingToken == null && clientTokenServices != null) {
existingToken = clientTokenServices.getAccessToken(resource, auth);
}
if (existingToken != null) {
if (existingToken.isExpired()) {
if (clientTokenServices != null) {
clientTokenServices.removeAccessToken(resource, auth);
}
OAuth2RefreshToken refreshToken = existingToken.getRefreshToken();
if (refreshToken != null && !resource.isClientOnly()) { // P2
accessToken = refreshAccessToken(resource, refreshToken, request);
}
}
else {
accessToken = existingToken;
}
}
}
As you can see at P1, the block is entered if either an authorized user session exists (auth
) or the resource is configured as clientOnly
. As I do not have users but I am in a linked service scenario, I have isClientOnly() == true && auth == null
. But at P2 the final decision upon actually doing the refresh is contraticted by requiring !isClientOnly()
. So this effectively bans refresh requests in client only scenarios.
This was the way to go in versions before 2.2.1 and I have found out, that this seems to be a fix to the following Issue. To me this seems plain wrong.
Furthermore, to me the patch appears to break client functionality to fix an actual server misbehavior. As you can see in the issue discussion, I have already commented the there. But as that issue is closed and the spring-security-oauth2 forum states that discussions should be held here on StackOverflow, I am asking for help here.
Again the question: How should a client application be configured to consume OAuth2 secured services via OAuth2RestTemplate and an access token runtime of an hour and refresh token runtime of lets say two hours.