I'm using Spring security and spring oauth to authenticate in my web app (using jwt tokens). This works fine, tokens get exchanged and I can log in correctly. However, once logged in the authentication does not expire even though the token does. When I try to reuse the token to get resources from my resource server it returns an access denied because the token is no longer valid.
My web app is a stateful (vaadin) webapp. It uses the session for lots of stuff, I cannot get around using it. After authentication using OAuth it will use "Previously Authenticated: org.springframework.security.oauth2.provider.OAuth2Authentication" to check if it was authenticated and I seems this will just be "true" until the session is destroyed.
My rest api is state/sessionless and thus will correctly verify the token every time. It will give a 401 if it is expired.
The only way I have found to handle this is rather ugly: Invalidate the session if the api returns 401. However what I would like to see is that the web app also checks the validity of the token on each request. Is there a way to do this when using sessions?
Here is the oauth security config part for my webapp.
@Configuration
@EnableOAuth2Sso
public class OAuthConfig extends WebSecurityConfigurerAdapter
{
@Override
protected void configure(HttpSecurity http) throws Exception
{
http.csrf().disable()
.authorizeRequests()
.antMatchers("/login**").permitAll()
.anyRequest().authenticated()
.and()
.logout()
.logoutSuccessUrl("/")
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.and()
.exceptionHandling().authenticationEntryPoint((request, response, authException) -> response.sendRedirect("/login"));
}
}
I've spent a day investigating this issue and the mechanism of OAuth2 in Spring in general. I can confirm that the findings above that the access token is not refreshed after it has expired are correct. The object Authentication never changes before or after the token has expired. But I found an intricate way to refresh the token without sending 401. I used some code from this tutorial
This is how. First create a custom filter:
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
OAuth2ClientContext ctxt = restTemplate.getOAuth2ClientContext();
if (ctxt != null) {
OAuth2AccessToken accessToken = ctxt.getAccessToken();
if (accessToken != null && accessToken.isExpired()) {
SecurityContextHolder.getContext().setAuthentication(null);
}
}
chain.doFilter(request, response);
}
This filter sets the Authentication object to null if the token has expired. This filter must be registered before the AnonymousAuthenticationFilter:
http.addFilterBefore(customFilter, AnonymousAuthenticationFilter.class);
The AnonymousAuthenticationFilter will fill in the Authentication with "anonymous" properties. Then the last filter will throw a AccessDeniedException and commence the process of getting the token from the server. The AccessDeniedException can begin the process of getting the new token only if the Authentication object is filled in with "anonymous" properties. The server actually refreshes the token and sends back a new token with a new expiration time.
But the question remains if this property was ever intended to be used this way. Maybe this expiration time is the time between the time when the token was issued and before it is received by the client? If the token arrives after it has expired an exception is thrown. Maybe this is how it is supposed to work?
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