I am currently refactoring the security configuration removing WebSecurityConfigurerAdapter
and am currently stuck on a config using two Basic Auth configurations with different user stores on different paths.
Current configuration looks like this and works fine:
@EnableWebSecurity
public class SecurityConfig {
@Order(1)
@Configuration
public static class BasicSpecialAuth extends WebSecurityConfigurerAdapter {
// some code
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// some code
auth.inMemoryAuthentication().withUser(specialUser.getId()).password(passwordEncoder().encode(specialUser.getPassword())).roles("SPECIALROLE");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic()
.and()
.antMatcher("/very-special-path/**")
//. more code
.authorizeRequests(r -> r
.anyRequest().authenticated());
}
}
@Order(2)
@Configuration
public static class BasicAppAuth extends WebSecurityConfigurerAdapter {
// some code
@Bean
public CustomUserDetailsService customUserDetailsService() {
return new CustomUserDetailsService(userRepository);
}
@Override
protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customUserDetailsService())
.passwordEncoder(encoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic()
.and()
//. more code
.authorizeRequests(auth -> auth
.anyRequest().authenticated());
}
}
}
As can be seen, /very-special-path
uses InMemoryAuthentication
set up at start by configuration.
All other paths should be authenticated using users from local database. Due to possible duplicates on usernames I am not able to use the database for /very-special-path
users too. Requirement is to have these separated.
Following documentation it was quite simple to change this on our apps providing Basic Auth and JWT Auth on different path. But with both using Basic Auth and different user stores, I have no idea how to set up configuration properly.
Any help would be appreciated.
Edit, the current config:
@Configuration
public class SecurityConfig {
// some code
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public UserDetailsService customUserDetailsService() {
return new CustomUserDetailsService(userRepository);
}
@Bean
public InMemoryUserDetailsManager inMemoryUserDetailsService() {
// more code
UserDetails healthUser = User.withUsername(specialUser.getId())
.password(passwordEncoder().encode(specialUser.getPassword()))
.roles("SPECIALROLE")
.build();
return new InMemoryUserDetailsManager(healthUser);
}
@Bean
@Order(1)
public SecurityFilterChain specialFilterChain(HttpSecurity http) throws Exception {
http.httpBasic()
.and()
.antMatcher("/very-special-path/**")
.authorizeRequests(auth -> auth
.anyRequest().authenticated());
return http.build();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.httpBasic()
.and()
.authorizeRequests(auth -> auth
.anyRequest().authenticated());
return http.build();
}
}
The app starts without any Warning or Error.
Both chains are mentioned in the log:
o.s.s.web.DefaultSecurityFilterChain : Will secure any request with ..
o.s.s.web.DefaultSecurityFilterChain : Will secure Ant [pattern='/very-special-path/**'] with ..
But authentication does not work. Checked for different endpoints and with different users. Every request gets an 401
.
This config misses the assignment of the UserDetails to the specific filter chain. Is there a way to do so?
CodePudding user response:
Based on the extra information you provided, there are only a couple of tweaks needed to get your configuration working. Take a look at this blog post that details common patterns for replacing WebSecurityConfigurerAdapter
with the component-based approach, specifically the local AuthenticationManager.
For the special/health user, you can manually construct a local authentication manager so as not to collide with the global one declared as an @Bean
. Here's a full example:
@EnableWebSecurity
public class SecurityConfig {
// ...
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public UserDetailsService customUserDetailsService() {
return new CustomUserDetailsService(userRepository);
}
@Bean
@Order(1)
public SecurityFilterChain specialFilterChain(HttpSecurity http, PasswordEncoder passwordEncoder) throws Exception {
http
.mvcMatcher("/very-special-path/**")
.authorizeRequests((authorize) -> authorize
.anyRequest().authenticated()
)
.httpBasic(Customizer.withDefaults())
.authenticationManager(specialAuthenticationManager(passwordEncoder));
return http.build();
}
private AuthenticationManager specialAuthenticationManager(PasswordEncoder passwordEncoder) {
UserDetailsService userDetailsService = specialUserDetailsService(passwordEncoder);
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setPasswordEncoder(passwordEncoder);
authenticationProvider.setUserDetailsService(userDetailsService);
return new ProviderManager(authenticationProvider);
}
private UserDetailsService specialUserDetailsService(PasswordEncoder passwordEncoder) {
UserDetails specialUser = User.withUsername("specialuser")
.password(passwordEncoder.encode("specialpassword"))
.roles("SPECIALROLE")
.build();
return new InMemoryUserDetailsManager(specialUser);
}
@Bean
@Order(2)
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests((authorize) -> authorize
.anyRequest().authenticated()
)
.httpBasic(Customizer.withDefaults());
return http.build();
}
}