Search code examples
javaspringspring-bootspring-securityspring-saml

How to execute a filter or handler only once after successful SAML2 login?


I am using Spring Security with SAML2 service provider and have a requirement to set custom tokens as part of cookies in the response after a successful SAML2 login.I have created a filter SetToken and configure to call after Saml2WebSsoAuthenticationFilter. The filter is getting called every time, whereas our requirement is to set custom cookie once after the SAML2 login is successful.

Here is my Spring configuration class:

@Configuration
public class SAMLConfiguration {

    @Autowired
    SetToken token;

    @Bean
    SecurityFilterChain configure(HttpSecurity http) throws Exception {
        OpenSaml4AuthenticationProvider authenticationProvider = new OpenSaml4AuthenticationProvider();

        Saml2MetadataFilter filter = new Saml2MetadataFilter(relyingPartyRegistrationRepository, new OpenSamlMetadataResolver());
        http.addFilterBefore(filter, Saml2WebSsoAuthenticationFilter.class);

        http.authorizeHttpRequests(requests -> requests
                .requestMatchers("/saml2/service-provider-metadata/**")
                .permitAll()
        ).addFilterAfter(token, Saml2WebSsoAuthenticationFilter.class)
        .addFilterAfter(refresh, SecurityContextHolderFilter.class)
        .saml2Login(saml2 -> saml2
                .authenticationManager(new ProviderManager(authenticationProvider))
                .relyingPartyRegistrationRepository(relyingPartyRegistrationRepository)
        ).saml2Logout(withDefaults());
    }
}

The SetToken filter, which is supposed to be called after SAML2 authorization, is defined as follows, this class is getting called every time which we do not want as token setting happens only once.

@Component
public class SetToken extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // Get principle and make an API call to a third-party system to set the cookie
        // SecurityContextHolder.getContext().getAuthentication().getPrincipal();

        response.addCookie(customCookie);
        filterChain.doFilter(request, response);
    }
}

I also tried setting the session authentication strategy in the configuration, but it is also getting invoked in every request after a successful SAML2 login. We want to attach a handler that is only invoked once after a successful SAML2 login.

http.sessionManagement()
    .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
    .sessionAuthenticationStrategy(tokenStrategy);

The TokenStrategy class implements SessionAuthenticationStrategy and is defined as follows:

public class TokenStrategy implements SessionAuthenticationStrategy {

    @Override
    public void onAuthentication(
            Authentication authentication,
            HttpServletRequest request,
            HttpServletResponse response
    ) throws SessionAuthenticationException {
        // Custom logic to get the cookie
        response.addCookie(customCookie);
    }
}

How can I attach a one-time event listener that is executed only after a successful SAML2 login? Is there a way to have a filter that is only called after the SAML2 filter successfully logs in and is not invoked in any other api/url calls?


Solution

  • As documented Spring Boot will automatically register any Filter, Servlet etc. with the servlet container. When this happens the Filter will be registered twice (it executes once because it extends OncePerRequestFilter!) once as part of the regular chain and next to the embedded Spring Security Filter Chain.

    As the regular chain always executes before the embedded Spring Security one it will thus always execute before that.

    To prevent the registration add a FilterRegistrationBean to tell Spring Boot to ignore this filter for registration. In short set enabled to false.

    @Bean
    public FilterRegistrationBean<SetToken> setTokenRegistrationBean(SetToken filter) {
      var registration = new FilterRegistrationBean(filter);
      registration.setEnabled(false);
      return registration;
    }
    

    This will prevent Spring Boot from automatically adding it to the normal filter chain. It will now execute only as part of the security chain. Assuming that your SAML filter throws an exception of some sort instead of proceeding the filter chain this should now only execute after authentication.

    Duplicate/Similar answers/questions