I have two sets of endpoints: /api/** and /ui/**, each one will have a separated authentication mechanism. For the sake of the simplification for this question: both will basically use JWT but with different secrets. I have the below configuration:
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, proxyTargetClass = true)
public class SecurityConfig {
@Order(1)
@Configuration
public static class SecurityConfigApi {
@Autowired
private CustomUserDetailsService userDetailsService;
@Autowired
private UnauthorizedHandler unauthorizedHandler;
@Autowired
private PasswordEncoder passwordEncoder;
@Bean
public AuthTokenFilterApi authenticationJwtTokenFilterApi() {
return new AuthTokenFilterApi();
}
@Bean
public DaoAuthenticationProvider authenticationProviderApi() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService);
authProvider.setPasswordEncoder(passwordEncoder);
return authProvider;
}
@Bean
public AuthenticationManager authenticationManagerApi(
AuthenticationConfiguration authConfig) throws Exception {
return authConfig.getAuthenticationManager();
}
@Bean
public SecurityFilterChain filterChainApi(HttpSecurity http) throws Exception {
http.antMatcher("/api/**")
.cors()
.and()
.csrf()
.disable()
.exceptionHandling()
.authenticationEntryPoint(unauthorizedHandler)
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/**").authenticated();
http.addFilterBefore(authenticationJwtTokenFilterApi(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
@Order(2)
@Configuration
public static class UiSecurityConfig {
@Autowired
private CustomUserDetailsService userDetailsService;
@Autowired
private UnauthorizedHandler unauthorizedHandler;
@Autowired
private PasswordEncoder passwordEncoder;
@Bean
public AuthTokenFilter authenticationJwtTokenFilterUi() {
return new AuthTokenFilter();
}
@Bean
public DaoAuthenticationProvider authenticationProviderUi() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService);
authProvider.setPasswordEncoder(passwordEncoder);
return authProvider;
}
@Bean
public AuthenticationManager authenticationManagerUi(
AuthenticationConfiguration authConfig) throws Exception {
return authConfig.getAuthenticationManager();
}
@Bean
public SecurityFilterChain filterChainUi(HttpSecurity http) throws Exception {
http.antMatcher("/ui/**")
.cors()
.and()
.csrf()
.disable()
.exceptionHandling()
.authenticationEntryPoint(unauthorizedHandler)
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/ui/auth/login")
.permitAll()
.antMatchers("/ui/auth/signup")
.hasRole("ADMIN")
.antMatchers("/ui/auth/refresh-token")
.hasAnyRole();
http.addFilterBefore(authenticationJwtTokenFilterUi(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
}
And the filter (you can assume the other one is similar to this):
public class AuthTokenFilterApi extends OncePerRequestFilter {
private static final Logger logger = LoggerFactory.getLogger(AuthTokenFilterApi.class);
@Autowired
private JwtUtilsApi jwtUtils;
@Autowired
private CustomUserDetailsService userDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
try {
String jwt = jwtUtils.getJwtFromHttpRequest(request);
if (jwt != null && jwtUtils.validateJwtToken(jwt)) {
String username = jwtUtils.getUserNameFromJwtToken(jwt);
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(userDetails,
null,
userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
} catch (Exception e) {
logger.error("Cannot set user authentication.", e);
}
filterChain.doFilter(request, response);
}
}
It seems to be working OK, the calls are getting correctly authenticated by the correct filter but I noticed that after the authentication happens, the filter for the other enpoint is still being executed.
Is there a way to prevent this? Is this the correct behavior of Spring Security? I can always see both filters when I inspect the FilterChain but I thought that as I have two different configured security contexts, that would not happen.