Search code examples
javaspring-bootauthenticationspring-securityspring-security-saml2

JWT authentication with fallback to SAML2 for the same path


I'm using spring-security-saml2-service-provider for authentication in one of my spring boot applications and I'm using a custom JwtAuthorizationFilter (via a http Authentication header) in a different spring boot application.

They both work perfectly on their own.

Now I need to write a spring boot application that uses both of them. If the JWT token is available (Authentication header), then use the JwtAuthorizationFilter, otherwise use saml2Login.

The SAML2 configuration looks like this: (There is no filter, just the saml2Login)

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .csrf()
            .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()).and()
            .antMatcher("/**").authorizeRequests()
            .antMatchers("/saml2/service-provider-metadata/**").permitAll()
            .antMatchers("/**").authenticated().and()

            // use SAML2
            .saml2Login()
            .addObjectPostProcessor(new ObjectPostProcessor<OpenSamlAuthenticationProvider>() {
                public <O extends OpenSamlAuthenticationProvider> O postProcess(O samlAuthProvider) {
                    samlAuthProvider.setAuthoritiesExtractor(authoritiesExtractor());
                    samlAuthProvider.setAuthoritiesMapper(authoritiesMapper());
                    return samlAuthProvider;
                }
            })
        ;
    }

The JWT configuration looks like this:

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .csrf()
            .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()).and()
            .antMatcher("/**").authorizeRequests()
            .antMatchers("/**").authenticated().and()

            // use JWT
            .addFilter(new JwtAuthorizationFilter(authenticationManager(), jwtUtil))
        ;
    }

I think I need something like a JwtOrSaml2AuthenticationFilter but don't know how to do that.


Solution

  • The solution is to

    1. Duplicate the configuration with @Order and
    2. Set a header based requestMatcher before the addFilter

      @EnableWebSecurity
      public class SecurityConfiguration {
          @Order(100) // lower number = higher priority
          @Configuration
          @RequiredArgsConstructor
          public static class AppSecurityJWT extends WebSecurityConfigurerAdapter {
              final JWTUtil jwtUtil;
      
              @Override
              protected void configure(HttpSecurity http) throws Exception {
                  http.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()).and()
                      .antMatcher("/**").authorizeRequests()
                      .antMatchers("/saml2/service-provider-metadata/**", "/idm-app/**").permitAll()
                      .antMatchers("/**").authenticated().and()
      
                      // This configuration will only be active if the Authorization header is present in the request
                      .requestMatcher(new RequestHeaderRequestMatcher("Authorization")).addFilter(new JwtAuthorizationFilter(authenticationManager(), jwtUtil))
                  ;
              }
          }
      
          @Order(101)
          @Configuration
          @RequiredArgsConstructor
          public static class AppSecuritySAML2 extends WebSecurityConfigurerAdapter {
              @Override
              protected void configure(HttpSecurity http) throws Exception {
                  http
                      .csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()).and()
                      .antMatcher("/**").authorizeRequests()
                      .antMatchers("/saml2/service-provider-metadata/**", "/idm-app/**").permitAll()
                      .antMatchers("/**").authenticated().and()
      
                      // This whole configuration will only be active, if the previous (100) didn't match
                      .saml2Login()
                      //...
              ;
          }
      }