Spring Security with oidc: refresh the tokens
Asked Answered
I

4

16

Spring Boot 2 with Spring Security 5 can be configured to use an openID connect ID provider for authentication. I managed to setup up my project just by configuring Spring Security - that works fine with all kinds of perfectly preconfigured security mechanisms like mitigation of session fixation.

But it seems that Spring Security does not refresh the tokens (which are stored in the session) by itself when they are expired.

Is there a setting for that or do I have to care for the refresh myself?

Update: Spring Boot 2.1 has been released, so it is time to revisit this problem. I still have no clue if the accessToken can now be automatically refreshed or if I have to write code for doing so...

Indecipherable answered 10/8, 2018 at 15:55 Comment(0)
T
4

According to https://github.com/spring-projects/spring-security/issues/6742 it seems that the token is intentionally not refreshed:

An ID Token typically comes with an expiration date. The RP MAY rely on it to expire the RP session.

Spring does not. There are two enhancements mentioned at the end which should solve some of the refresh issues - both are still open.

As a workaround, I implemented a GenericFilterBean which checks the token and clears the authentication in the current security context. Thus a new token is needed.

@Configuration
public class RefreshTokenFilterConfig {

    @Bean
    GenericFilterBean refreshTokenFilter(OAuth2AuthorizedClientService clientService) {
        return new GenericFilterBean() {
            @Override
            public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
                Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
                if (authentication != null && authentication instanceof OAuth2AuthenticationToken) {
                    OAuth2AuthenticationToken token = (OAuth2AuthenticationToken) authentication;
                    OAuth2AuthorizedClient client =
                            clientService.loadAuthorizedClient(
                                    token.getAuthorizedClientRegistrationId(),
                                    token.getName());
                    OAuth2AccessToken accessToken = client.getAccessToken();
                    if (accessToken.getExpiresAt().isBefore(Instant.now())) {
                        SecurityContextHolder.getContext().setAuthentication(null);
                    }
                }
                filterChain.doFilter(servletRequest, servletResponse);
            }
        };
    }
}

Additionally I had to add the filter to the security config:

@Bean
public WebSecurityConfigurerAdapter webSecurityConfigurer(GenericFilterBean refreshTokenFilter) {
    return new WebSecurityConfigurerAdapter() {
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                   .addFilterBefore(refreshTokenFilter,  AnonymousAuthenticationFilter.class)

Implemented with spring-boot-starter-parent and dependencies in version 2.2.7.RELEASE:

  • spring-boot-starter-web
  • spring-boot-starter-security
  • spring-boot-starter-oauth2-client

I appreciate opinions about this workaround since I'm still not sure if such an overhead is really needed in Spring Boot.

Torrez answered 15/5, 2020 at 16:30 Comment(1)
For who ever still looking for solution, it is here docs.spring.io/spring-security/site/docs/5.1.2.RELEASE/… . WebClient is the remedy.Manganese
R
10

According to the documentation,

https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#webclient

When using a WebClient configured correctly, as given in the documentation it will automatically be refreshed.

Spring Security will automatically refresh expired tokens (if a refresh token is present)

This is also supported by the features matrix that refresh tokens are supported.

https://github.com/spring-projects/spring-security/wiki/OAuth-2.0-Features-Matrix

There was an older blog on Spring Security 5 that gives you access to beans that you could do this manually,

Authentication authentication =
    SecurityContextHolder
        .getContext()
        .getAuthentication();

OAuth2AuthenticationToken oauthToken =
    (OAuth2AuthenticationToken) authentication;

There will be an OAuth2AuthorizedClientService automatically configured as a bean in the Spring application context, so you’ll only need to inject it into wherever you’ll use it.

OAuth2AuthorizedClient client =
    clientService.loadAuthorizedClient(
            oauthToken.getAuthorizedClientRegistrationId(),
            oauthToken.getName());

String refreshToken = client.getRefreshToken();

And, failing to find it right now, but I assume as part of the OAuth2AuthorizedClientExchangeFilterFunction has the calls to do a refresh.

Repertory answered 7/11, 2018 at 18:44 Comment(1)
thanx for this detailed answer!Indecipherable
T
4

According to https://github.com/spring-projects/spring-security/issues/6742 it seems that the token is intentionally not refreshed:

An ID Token typically comes with an expiration date. The RP MAY rely on it to expire the RP session.

Spring does not. There are two enhancements mentioned at the end which should solve some of the refresh issues - both are still open.

As a workaround, I implemented a GenericFilterBean which checks the token and clears the authentication in the current security context. Thus a new token is needed.

@Configuration
public class RefreshTokenFilterConfig {

    @Bean
    GenericFilterBean refreshTokenFilter(OAuth2AuthorizedClientService clientService) {
        return new GenericFilterBean() {
            @Override
            public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
                Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
                if (authentication != null && authentication instanceof OAuth2AuthenticationToken) {
                    OAuth2AuthenticationToken token = (OAuth2AuthenticationToken) authentication;
                    OAuth2AuthorizedClient client =
                            clientService.loadAuthorizedClient(
                                    token.getAuthorizedClientRegistrationId(),
                                    token.getName());
                    OAuth2AccessToken accessToken = client.getAccessToken();
                    if (accessToken.getExpiresAt().isBefore(Instant.now())) {
                        SecurityContextHolder.getContext().setAuthentication(null);
                    }
                }
                filterChain.doFilter(servletRequest, servletResponse);
            }
        };
    }
}

Additionally I had to add the filter to the security config:

@Bean
public WebSecurityConfigurerAdapter webSecurityConfigurer(GenericFilterBean refreshTokenFilter) {
    return new WebSecurityConfigurerAdapter() {
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                   .addFilterBefore(refreshTokenFilter,  AnonymousAuthenticationFilter.class)

Implemented with spring-boot-starter-parent and dependencies in version 2.2.7.RELEASE:

  • spring-boot-starter-web
  • spring-boot-starter-security
  • spring-boot-starter-oauth2-client

I appreciate opinions about this workaround since I'm still not sure if such an overhead is really needed in Spring Boot.

Torrez answered 15/5, 2020 at 16:30 Comment(1)
For who ever still looking for solution, it is here docs.spring.io/spring-security/site/docs/5.1.2.RELEASE/… . WebClient is the remedy.Manganese
S
2

In case someone get there looking for the answer for newer version of Spring. Currently we have Spring 6. I am using oauth2Login() feature to log in with the use of socials like github, facebook. I wanted to tie user session to access token expireAt property. If expired, should refresh token and try to extend the session before force user to log in once again. Non Reactive solution (Without WebClient and OAuth2AuthorizedClientExchangeFilterFunction) within Spring doesn't provide auto mechanism in oauth2Login() feature. But I configured a bean for OAuth2AuthorizedClientManager which can be used with oauth2Client() feature with Spring reactive. However it's probably a cool way to maually refresh the access token and clear manually an authentication context in case of exception.

    @Bean
        public OAuth2AuthorizedClientManager authorizedClientManager() {
            OAuth2AuthorizedClientProvider authorizedClientProvider =
                    OAuth2AuthorizedClientProviderBuilder.builder()
                            .authorizationCode()
                            .refreshToken()
                            .build();
    
            DefaultOAuth2AuthorizedClientManager authorizedClientManager =
                    new DefaultOAuth2AuthorizedClientManager(clientRegistrationRepository(), authorizedClientRepository());
            authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
            return authorizedClientManager;
        }


and Filter


    public class ExpiredTokenFilter extends OncePerRequestFilter {
    
        @Resource
        private OAuth2AuthorizedClientManager authorizedClientManager;
    
        @Resource
        private OAuth2AuthorizedClientRepository authorizedClientRepository;
    
        @Override
        protected void doFilterInternal(
                @Nonnull HttpServletRequest request, @Nonnull HttpServletResponse response,
                @Nonnull FilterChain filterChain)
                throws ServletException, IOException {
            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
            if (Objects.nonNull(authentication) && authentication.getPrincipal() instanceof OidcUser) {
                validateToken(authentication, request, response);
            }
    
            filterChain.doFilter(request, response);
        }
    
        private void validateToken(Authentication auth, HttpServletRequest request, HttpServletResponse response) {
            OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest
                    .withClientRegistrationId(MY_PROVIDER)
                    .principal(auth)
                    .attributes(attrs -> {
                        attrs.put(HttpServletRequest.class.getName(), request);
                        attrs.put(HttpServletResponse.class.getName(), response);
                    })
                    .build();
            try {
                authorizedClientManager.authorize(authorizeRequest);
            } catch (ClientAuthorizationException e) {               
authorizedClientRepository.removeAuthorizedClient(MY_PROVIDER, auth, request, response);
SecurityContextHolder.getContext().setAuthentication(null);
            }
        }
    }

Remember to register Filter in SecurityFilterChain

http.addFilterBefore(expiredTokenFilter, AbstractPreAuthenticatedProcessingFilter.class));
Superclass answered 13/9, 2023 at 7:40 Comment(0)
I
0

even a bounty of 100 rep points did not yield an answer. So I guess there is currently no mechanism implemented to automatically refresh the access token with Spring Security.

A valid alternative seems to use the spring boot keycloak adapter which is capable of refreshing the token.

Indecipherable answered 7/11, 2018 at 15:24 Comment(8)
THe latest docs.spring.io/spring-security/site/docs/current/reference/… documenation states that if given a refresh token spring security should automatically attempt to refresh it. github.com/spring-projects/spring-security/wiki/… also supports that it is supportedRepertory
@DarrenForsythe thanx for the links - how about sating this as an answer so that I can assign the bounty to you?Indecipherable
so it seems that the web client handles the refresh token. So, as I do understand, the app gets a request on an endpoint and only if the controller tried to use the access token via a webClient, the refresh token will be used. Interesting concept... The Keycloak adapter already refreshes the accesstoken with the incoming request...Indecipherable
I'm unsure of the underlying implementation of the support for getting of and refresh etc. of tokens with the webclient, which Im currently running into an issue with myself, but from what Ive seen of the code if you've authenticated it would try to refresh if the refresh token available auth object on a request for a given provider. It is also possible to get a hold of lower level beans and do it yourself, spring.io/blog/2018/03/06/… has an example of thatRepertory
any idea if how to do it with new spring security release ??? Can we use any custom filter to refresh token and update the principal ?Corri
@agam : try to avoid custom filters. everything should be possible with settings, but they are sometimes hidden. I got best results with keycloak adaptors instead of spring security.Indecipherable
Thanks. I will take a look at keycloak adaptor. Meanwhile I was able to do that with custom filter. As you said I should avoid it, any specific reason why??Corri
I have added my solution here. would you like to have a look? #59144660Corri

© 2022 - 2024 — McMap. All rights reserved.