Search code examples
spring-securityjwtrefresh-tokenopenid-connect

Spring Security with oidc: refresh the tokens


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...


Solution

  • 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.