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();
}