Skip to content
This repository has been archived by the owner on May 31, 2022. It is now read-only.

@EnableOAuth2Sso not taking care of refreshing the Access Token #5

Closed
rowe42 opened this issue Feb 22, 2018 · 15 comments
Closed

@EnableOAuth2Sso not taking care of refreshing the Access Token #5

rowe42 opened this issue Feb 22, 2018 · 15 comments
Assignees

Comments

@rowe42
Copy link

rowe42 commented Feb 22, 2018

We are using a Spring Boot Application (in our case a Zuul Gateway) with the @EnableOAuth2Sso annotation. For this we are having a separate Auth Server, which delivers JWT-based Access-Token through the Authorization Code Flow.

When accessing the application the first time, we are correctly forwarded to the Auth Server and back to the application after login, which then fetches the Access-Server as expected.

On subsequent calls to the Spring Boot Application this Forwarding is not executed, since the Access-Token is now bound to the Application Session.

However, what we do NOT see is that the Access-Token is inspected for validity (i.e. checking of the "exp" claim), so the Access-Token is never refreshed.

Note: When calling another Microservice in the backend through the Gateway, we see that the OAuth2TokenRelayFilter from @dsyer IS in fact refreshing the Access Token before calling the backend Microservice. However, this Access-Token is NOT replacing the expired Access-Token in the API-Gateway, which leads to a refresh of the Access-Token on EVERY backend-call (which of course is not correct).

What are we doing wrong? Is there some setting in the API-Gateway which we are missing?

We are having this bean, which doesn't help:

    @Bean
    @Primary
    public DefaultTokenServices tokenServices() {
        DefaultTokenServices defaultTokenServices = new DefaultTokenServices();        
        defaultTokenServices.setTokenStore(tokenStore());
        defaultTokenServices.setSupportRefreshToken(true);
        return defaultTokenServices;
    }

@dsyer Maybe you can give us a hint whether we are missing some setting somewhere?

@rowe42
Copy link
Author

rowe42 commented Feb 24, 2018

Still struggling with this. There seems to be a lot of code in the Spring classes to handle refreshing of tokens, so I'm pretty sure our configuration is wrong somehow.

I found out that DefaultTokenServices.loadAuthentication does reject access-tokens which are already expired. However, as soon as the access-token is accepted and the authentication information is placed in the SecurityContext, no further validation seems to happen on subsequent calls.

I also found out this has nothing to do with the Zuul gateway.

The important thing is that the browser is NOT sending tokens in the header. Instead the user is verified by the session-id.

Our current approach can be found here: https://github.com/xdoo/lhm_animad_admin_api_gateway/tree/_%23197/src/main/java/de/muenchen/referenzarchitektur/apigateway
especially in class OAuth2ResourceServerConfig

What could it be we are missing so Spring Security does not check for expiry on every call?

@jgrandja
Copy link
Contributor

jgrandja commented Feb 27, 2018

@rowe42 Can you please provide me a sample that reproduces this issue? This will be the most efficient method for troubleshooting. I'll take a look at lhm_animad_admin_api_gateway in the meantime and let me know if there are other repos to look at.

@rowe42
Copy link
Author

rowe42 commented Mar 1, 2018

@jgrandja Thanks for your reply!
I've now made a showcase that is stripped of all the details of lhm_animad_admin_api_gateway.
You can find it here: https://github.com/rowe42/oauth2_showcase
The Readme contains an extensive explanation how to run it.

I've created a Spring Autorization Server with it so you will NOT need to install a KeyCloak server. Everything you need to run the showcase is included in the Repository.

Please get back to me if you have any questions!

@jgrandja
Copy link
Contributor

jgrandja commented Mar 5, 2018

@rowe42 Thanks for preparing the sample. The @EnableOAuth2Sso annotation is working as expected but seems like you are expecting it to work differently?

Take a look at the reference, as it states the following:

An OAuth2 Client can be used to fetch user details from the provider (if such features are available) and then convert them into an Authentication token for Spring Security.

The main goal of @EnableOAuth2Sso is to allow user's to authenticate to the application using an external OAuth 2.0 provider. This is a very convenient authentication option an application can provide as it doesn't need to provide it's own authentication mechanism (e.g. httpSecurity.formLogin()) and instead leverages an external identity (OAuth 2.0) provider for authentication.

The @EnableOAuth2Sso annotation registers the OAuth2ClientAuthenticationProcessingFilter, which uses a RestTemplate (the OAuth Client) to obtain an Access Token from the Provider. After the Client receives the Access Token from the Provider, it would typically call the UserInfo Endpoint to fetch user information in order to create the Authentication. However, since you are using JWT based tokens, the DefaultAccessTokenConverter.extractAuthentication() is called (used by JwtAccessTokenConverter) to extract the information (using user_name and authorities claims) to create the OAuth2Authentication and establish an authenticated session. The Access Token used to obtain the user information is used once only during authentication. After the user is authenticated, that Access Token is never used again. Moreover, the refresh_token grant is not applicable for this specific OAuth Client scenario (flow).

The OAuth2RestTemplate does support refresh_token grant, but you are not explicitly using it in your flows. The OAuth2RestTemplate represents an OAuth 2.0 Client and it's main responsibility is to call protected resources (at Resource Servers) with an Access Token. If the Access Token is expired, it will refresh the Access Token if it was previously granted a Refresh Token.

I noticed your application is configured with @EnableOAuth2Sso and @EnableResourceServer. Typically @EnableResourceServer is in a separate application and is called by an OAuth2RestTemplate to access the protected resources.

I hope this clarifies things?

I'm going to close this issue as @EnableOAuth2Sso is working as expected.

@rowe42
Copy link
Author

rowe42 commented Mar 10, 2018

Thanks for the exhaustive reply! I do understand what you are saying.
However, I still think it is not right to only inspect the access token once and then never again on subsequent requests.
That would mean basically that a user could use an application forever as long as she keeps the web session alive all the time. But that is not the intention with oauth, where the access token is supposed to be renewed regularily, but transparent to the user by using the refresh token. If the user should NOT be allowed to use the application any more this can be easily achieved by invalidating the refresh token.

@rowe42
Copy link
Author

rowe42 commented Mar 10, 2018

I know that the OAuth2RestTemplate fetches new access tokens when calling the backend, e.g. Within a zuul gateway filter. However, this new access token does obviously NEVER replace the access token in the web session so the access token is basically refreshed on every backend call!

@jgrandja
Copy link
Contributor

@rowe42

However, I still think it is not right to only inspect the access token once and then never again on subsequent requests.

After the user is authenticated via the authorization_code grant flow implemented by @EnableOAuth2Sso, the user has established an authenticated session with the application. At this point, any subsequent requests are between the browser agent and session within the application. This is NOT an OAuth Protected Resource request given that there is no OAuth Client in play here and therefore no access token is being passed from the browser to the application session. This is strictly an authenticated web session request.

I'm not familiar with the issue you are having with zuul gateway filter but you should log a ticket to the appropriate Spring Cloud project to address that if you require.

@stereo720712
Copy link

stereo720712 commented Jun 22, 2018

execuse me , for the issue :spring-attic/spring-cloud-security#61
i try for the uaa example.
But why when access token is expired, the refresh token will failed.
Can anyone please help me ?
Thanks.

@stereo720712
Copy link

if use following code , direct not call refresh token ,
'@bean
@Autowired
UserInfoRestTemplateCustomizer userInfoRestTemplateCustomizer(final LoadBalancerInterceptor loadBalancerInterceptor) {
return template -> {
List interceptors = new ArrayList<>();
interceptors.add(loadBalancerInterceptor);

        AccessTokenProviderChain accessTokenProviderChain = Stream
                .of(new AuthorizationCodeAccessTokenProvider(), new ImplicitAccessTokenProvider(),
                        new ResourceOwnerPasswordAccessTokenProvider(), new ClientCredentialsAccessTokenProvider())
                .peek(tp -> tp.setInterceptors(interceptors))
                .collect(Collectors.collectingAndThen(Collectors.toList(), AccessTokenProviderChain::new));
        
        template.setAccessTokenProvider(accessTokenProviderChain);
    };
}'

but default Oauth2RestTemplate will matain the refresh token to get new token.

@fschollmeyer
Copy link

Not sure if this is still relevant for anyone, but using spring boot 2.0.5 and cloud Finchley.SR1 I observed the same problem. Took me quite some time, to figure out, that the OAuth2TokenRelayFilter only refreshes the token, if a OAuth2RestOperations is configured as bean.

@Bean public OAuth2RestOperations restTemplate(OAuth2ClientContext clientContext, OAuth2ProtectedResourceDetails resourceDetails) { return new OAuth2RestTemplate(resourceDetails, clientContext); }

Adding this, even when I do not use it explicity, sorted out the issue. The zuul proxy now refreshes the token as expected.

@rowe42
Copy link
Author

rowe42 commented Oct 2, 2018

It was also my observation that OAuth2TokenRelayFilter needs OAuth2RestOperations (or similar) in order to refresh the access token.
However, be careful here: As I see it, OAuth2TokenRelayFilter does indeed fetch a new access token, but does not store it in the OAuth2Authentication Object in the Security Context. This has the effect that every request to the backend fetches a new access token instead of only fetching it when it is expired.
You can see this by debugging OAuth2TokenRelayFilter.

In order to solve this we have in fact created a custom Zuul Filter, which does refresh the access token AND store it in the OAuth2Authentication. By doing so the OAuth2TokenRelayFilter will always get a valid access token.

@fschollmeyer
Copy link

Hi @rowe42
with the current version this seems to be fixed (or worked-around). Whilst the token is not updated in the SecurityContext, the rest template does store it in the OAuth2ClientContext. A call to OAuth2RestTemplate.getAccessToken() retrieves the token from there. Only if it is expired, the new one is actually loaded. When debugging, this seems to work for me.

@steklopod
Copy link

Have same issue.

@AnkitPandoh
Copy link

Hi @rowe42
I am using @EnableOAuth2Client since @EnableOAuth2Sso is deprecated. My problem is similar as yours. I am able to get the access token after successful authentication using authorization_code grant type. But for every next request, it is not invoking the OAuth2ClientAuthenticationProcessingFilter and not refreshing the access token even though it is not valid. How did you manange to get the refresh token while still using @EnableOAuth2Sso annotation?
Is there a GIT repo I can refer to?

@HendrikPemoeller
Copy link

Hi,
I had the same Issue. I was able to fix it by Updating 'org.springframework.boot' to version '2.2.1.RELEASE' from '2.1.8.RELEASE'.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Development

No branches or pull requests

7 participants