Spring Security mix form based and http basic authentication

48 Views Asked by At

I want to mix a standard form based authentication app with an API that uses http basic authentication like this:

  • /io/** should trigger http basic authentication with a pre defined in memory user
  • all other routes should be authenticated with form based authentication

Currently i have this:

@Configuration
@EnableWebSecurity
@Slf4j
public class SpringSecurityConfiguration {

    private final ThdAuthenticationDetails thdAuthenticationDetails;

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public UserDetailsService users() {
        return new InMemoryUserDetailsManager(User
                .builder()
                .username("user")
                .password(passwordEncoder().encode("1234"))
                .roles(Role.Api.getValue())
                .build());
    }


    @Autowired
    public SpringSecurityConfiguration(ThdAuthenticationDetails thdAuthenticationDetails) {

        this.thdAuthenticationDetails = thdAuthenticationDetails;
    }

    @Bean
    @Order(1)
    public SecurityFilterChain apiSpringSecurityFilterChain(HttpSecurity http) throws Exception {
        log.info("starting with api security ...");

        http
                .securityMatcher("/io/**")
                .authorizeHttpRequests(authorize -> authorize
                        .anyRequest().hasRole(Role.Api.getValue())
                )
                .userDetailsService(users())
                .httpBasic(withDefaults());

        return http.build();


    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

        log.info("going to form login security ...");
        http
                .authorizeHttpRequests(authorizationManagerRequestMatcherRegistry -> authorizationManagerRequestMatcherRegistry
                        .requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
                        .requestMatchers("/public/**").permitAll()                        
                        .requestMatchers("/administration/**").hasAnyAuthority(Role.Admin.getValue())
                        .requestMatchers("/search/**").hasAnyAuthority(Role.Admin.getValue())
                        .anyRequest().fullyAuthenticated())
                .csrf(httpSecurityCsrfConfigurer ->
                        httpSecurityCsrfConfigurer.ignoringRequestMatchers("/public/**"))
                .csrf(httpSecurityCsrfConfigurer ->
                        httpSecurityCsrfConfigurer.ignoringRequestMatchers("/public/**"))
                .formLogin(httpSecurityFormLoginConfigurer -> {
                    httpSecurityFormLoginConfigurer
                            .loginPage("/login").permitAll()
                            .defaultSuccessUrl("/index", false)
                            .failureUrl("/denied")
                            .authenticationDetailsSource(this.thdAuthenticationDetails)
                    ;
                })

                .logout(httpSecurityLogoutConfigurer -> {
                    httpSecurityLogoutConfigurer
                            .logoutUrl("/logout")
                            .invalidateHttpSession(true)
                            .logoutSuccessUrl("/login");
                })
                .exceptionHandling(httpSecurityExceptionHandlingConfigurer ->
                        httpSecurityExceptionHandlingConfigurer
                                .accessDeniedHandler(new CustomAccessDeniedHandler()));


        return http.build();
    }
}

Further - i have a custom authentication provider for form based authentication that does some internal stuff (ldap checking) etc.

So when i start the app, the login screen appears.

When i go to /io/heartbeat which is an actual controller route i will be redirected to the login page which is not what i want.

How can i make the http basic dialogue working with my given in memory UserDetailsService for /io/** routes and let the other routes be handled by form based login with my custom AuthenticationProvider implementation?

EDIT:

This is my logging.level.org.springframework.security=DEBUG output, when trying /io/hearbeat

2024-04-01T10:49:28.040+02:00 DEBUG 5328 --- [rg] [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : Securing GET /
2024-04-01T10:49:28.061+02:00 DEBUG 5328 --- [rg] [nio-8080-exec-1] o.s.s.w.a.AnonymousAuthenticationFilter  : Set SecurityContextHolder to anonymous SecurityContext
2024-04-01T10:49:28.077+02:00 DEBUG 5328 --- [rg] [nio-8080-exec-1] o.s.s.w.s.HttpSessionRequestCache        : Saved request http://localhost:8080/rg/?continue to session
2024-04-01T10:49:28.079+02:00 DEBUG 5328 --- [rg] [nio-8080-exec-1] o.s.s.web.DefaultRedirectStrategy        : Redirecting to http://localhost:8080/rg/login
2024-04-01T10:49:28.083+02:00 DEBUG 5328 --- [rg] [nio-8080-exec-2] o.s.security.web.FilterChainProxy        : Securing GET /login
2024-04-01T10:49:28.083+02:00 DEBUG 5328 --- [rg] [nio-8080-exec-2] o.s.security.web.FilterChainProxy        : Secured GET /login
2024-04-01T10:49:29.717+02:00 DEBUG 5328 --- [rg] [nio-8080-exec-2] o.s.s.w.a.AnonymousAuthenticationFilter  : Set SecurityContextHolder to anonymous SecurityContext
2024-04-01T10:49:29.852+02:00 DEBUG 5328 --- [rg] [nio-8080-exec-6] o.s.security.web.FilterChainProxy        : Securing GET /
2024-04-01T10:49:29.853+02:00 DEBUG 5328 --- [rg] [nio-8080-exec-6] o.s.s.w.a.AnonymousAuthenticationFilter  : Set SecurityContextHolder to anonymous SecurityContext
2024-04-01T10:49:29.856+02:00 DEBUG 5328 --- [rg] [nio-8080-exec-6] o.s.s.w.s.HttpSessionRequestCache        : Saved request http://localhost:8080/rg/?continue to session
2024-04-01T10:49:29.856+02:00 DEBUG 5328 --- [rg] [nio-8080-exec-6] o.s.s.web.DefaultRedirectStrategy        : Redirecting to http://localhost:8080/rg/login
2024-04-01T10:49:29.864+02:00 DEBUG 5328 --- [rg] [nio-8080-exec-7] o.s.security.web.FilterChainProxy        : Securing GET /login
2024-04-01T10:49:29.865+02:00 DEBUG 5328 --- [rg] [nio-8080-exec-7] o.s.security.web.FilterChainProxy        : Secured GET /login
2024-04-01T10:49:29.914+02:00 DEBUG 5328 --- [rg] [nio-8080-exec-7] o.s.s.w.a.AnonymousAuthenticationFilter  : Set SecurityContextHolder to anonymous SecurityContext
2024-04-01T10:49:29.981+02:00 DEBUG 5328 --- [rg] [nio-8080-exec-5] o.s.security.web.FilterChainProxy        : Securing GET /css/signin.css
2024-04-01T10:49:29.982+02:00 DEBUG 5328 --- [rg] [nio-8080-exec-5] o.s.security.web.FilterChainProxy        : Secured GET /css/signin.css
2024-04-01T10:49:30.021+02:00 DEBUG 5328 --- [rg] [nio-8080-exec-5] o.s.s.w.a.AnonymousAuthenticationFilter  : Set SecurityContextHolder to anonymous SecurityContext
2024-04-01T10:49:30.049+02:00 DEBUG 5328 --- [rg] [nio-8080-exec-8] o.s.security.web.FilterChainProxy        : Securing GET /images/rg.jpg
2024-04-01T10:49:30.049+02:00 DEBUG 5328 --- [rg] [nio-8080-exec-8] o.s.security.web.FilterChainProxy        : Secured GET /images/rg.jpg
2024-04-01T10:49:30.062+02:00 DEBUG 5328 --- [rg] [nio-8080-exec-8] o.s.s.w.a.AnonymousAuthenticationFilter  : Set SecurityContextHolder to anonymous SecurityContext
2024-04-01T10:49:38.350+02:00 DEBUG 5328 --- [rg] [nio-8080-exec-9] o.s.security.web.FilterChainProxy        : Securing GET /io/heartbeat
2024-04-01T10:49:38.351+02:00 DEBUG 5328 --- [rg] [nio-8080-exec-9] o.s.s.w.a.AnonymousAuthenticationFilter  : Set SecurityContextHolder to anonymous SecurityContext
2024-04-01T10:49:38.351+02:00 DEBUG 5328 --- [rg] [nio-8080-exec-9] o.s.s.w.s.HttpSessionRequestCache        : Saved request http://localhost:8080/rg/io/heartbeat?continue to session
2024-04-01T10:49:38.352+02:00 DEBUG 5328 --- [rg] [nio-8080-exec-9] s.w.a.DelegatingAuthenticationEntryPoint : Trying to match using RequestHeaderRequestMatcher [expectedHeaderName=X-Requested-With, expectedHeaderValue=XMLHttpRequest]
2024-04-01T10:49:38.352+02:00 DEBUG 5328 --- [rg] [nio-8080-exec-9] s.w.a.DelegatingAuthenticationEntryPoint : No match found. Using default entry point org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint@556717ef
2024-04-01T10:49:38.401+02:00 DEBUG 5328 --- [rg] [nio-8080-exec-9] o.s.security.web.FilterChainProxy        : Securing GET /error
2024-04-01T10:49:38.401+02:00 DEBUG 5328 --- [rg] [nio-8080-exec-9] o.s.s.w.a.AnonymousAuthenticationFilter  : Set SecurityContextHolder to anonymous SecurityContext
2024-04-01T10:49:38.402+02:00 DEBUG 5328 --- [rg] [nio-8080-exec-9] o.s.s.w.s.HttpSessionRequestCache        : Saved request http://localhost:8080/rg/error?continue to session
2024-04-01T10:49:38.402+02:00 DEBUG 5328 --- [rg] [nio-8080-exec-9] o.s.s.web.DefaultRedirectStrategy        : Redirecting to http://localhost:8080/rg/login
2024-04-01T10:49:38.408+02:00 DEBUG 5328 --- [rg] [io-8080-exec-10] o.s.security.web.FilterChainProxy        : Securing GET /login
2024-04-01T10:49:38.408+02:00 DEBUG 5328 --- [rg] [io-8080-exec-10] o.s.security.web.FilterChainProxy        : Secured GET /login
2024-04-01T10:49:38.425+02:00 DEBUG 5328 --- [rg] [io-8080-exec-10] o.s.s.w.a.AnonymousAuthenticationFilter  : Set SecurityContextHolder to anonymous SecurityContext
2024-04-01T10:49:38.466+02:00 DEBUG 5328 --- [rg] [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : Securing GET /css/signin.css
2024-04-01T10:49:38.467+02:00 DEBUG 5328 --- [rg] [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : Secured GET /css/signin.css
2024-04-01T10:49:38.495+02:00 DEBUG 5328 --- [rg] [nio-8080-exec-1] o.s.s.w.a.AnonymousAuthenticationFilter  : Set SecurityContextHolder to anonymous SecurityContext
2024-04-01T10:49:38.517+02:00 DEBUG 5328 --- [rg] [nio-8080-exec-2] o.s.security.web.FilterChainProxy        : Securing GET /images/rg.jpg
2024-04-01T10:49:38.517+02:00 DEBUG 5328 --- [rg] [nio-8080-exec-2] o.s.security.web.FilterChainProxy        : Secured GET /images/rg.jpg
2024-04-01T10:49:38.524+02:00 DEBUG 5328 --- [rg] [nio-8080-exec-2] o.s.s.w.a.AnonymousAuthenticationFilter  : Set SecurityContextHolder to anonymous SecurityContext

EDIT / SOLUTION As dur suggested below i had to permit some routes like /error in my config.

This is the working config:

@Bean
    @Order(1)
    public SecurityFilterChain apiSpringSecurityFilterChain(HttpSecurity http) throws Exception {
        log.info("starting with api security ...");

        http
                .securityMatcher("/io/**")
                .authorizeHttpRequests(authorize -> authorize
                        .requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
                        .requestMatchers("/public/**").permitAll()
                        .requestMatchers("/error/**").permitAll()
                        .anyRequest().hasRole(Role.Api.getValue())
                )
                .userDetailsService(users())
                .httpBasic(withDefaults());

        return http.build();


    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

        log.info("going to form login security ...");
        http
                .authorizeHttpRequests(authorizationManagerRequestMatcherRegistry -> authorizationManagerRequestMatcherRegistry
                        .requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
                        .requestMatchers("/public/**").permitAll()
                        .requestMatchers("/error/**").permitAll()
                        .requestMatchers("/invoice/**").hasAnyAuthority(Role.Admin.getValue(), Role.Manager.getValue(), Role.Engineer.getValue())
                        .requestMatchers("/administration/**").hasAnyAuthority(Role.Admin.getValue())
                        .requestMatchers("/search/**").hasAnyAuthority(Role.Admin.getValue())
                        .anyRequest().fullyAuthenticated())
                .csrf(httpSecurityCsrfConfigurer ->
                        httpSecurityCsrfConfigurer.ignoringRequestMatchers("/public/**"))
                .csrf(httpSecurityCsrfConfigurer ->
                        httpSecurityCsrfConfigurer.ignoringRequestMatchers("/public/**"))
                .formLogin(httpSecurityFormLoginConfigurer -> {
                    httpSecurityFormLoginConfigurer
                            .loginPage("/login").permitAll()
                            .defaultSuccessUrl("/index", false)
                            .failureUrl("/denied")
                            .authenticationDetailsSource(this.thdAuthenticationDetails)
                    ;
                })

                .logout(httpSecurityLogoutConfigurer -> {
                    httpSecurityLogoutConfigurer
                            .logoutUrl("/logout")
                            .invalidateHttpSession(true)
                            .logoutSuccessUrl("/login");
                })
                .exceptionHandling(httpSecurityExceptionHandlingConfigurer ->
                        httpSecurityExceptionHandlingConfigurer
                                .accessDeniedHandler(new CustomAccessDeniedHandler()));


        return http.build();
    }
0

There are 0 best solutions below