Spring WebFlux Security - Is it possible to configure multiple ServerAuthenticationEntryPoints on a SecurityWebFilterChain for different resources - spring-boot

I have a few different APIs in my spring webflux application that need to respond differently to failed authentication. I am trying to set different ServerAuthenticationEntryPoints for each API to handle these cases.
I found this example configuration that shows how to configure different AuthenticationWebFilter for different resources, which enables you to set ServerAuthenticationSuccessHandler and ServerAuthenticationFailureHandler individually, however I am not sure how to configure different ServerAuthenticationEntryPoints without having completely separate SecurityWebFilterChains.
If I have to configure separate SecurityWebFilterChains, how would I do that?
My SecurityWebFilterChain is currently configured like this - unfortunately you can't set the exceptionHandling individually and the second call to authenticationEntryPoint takes precedent:
#Bean
fun securityWebFilterChain(
http: ServerHttpSecurity,
userServerAuthenticationEntryPoint: ServerAuthenticationEntryPoint,
userAuthenticationWebFilter: AuthenticationWebFilter,
deviceServerAuthenticationEntryPoint: ServerAuthenticationEntryPoint,
deviceAuthenticationWebFilter: AuthenticationWebFilter,
serverSecurityContextRepository: ServerSecurityContextRepository,
authenticationManager: ReactiveAuthenticationManager,
serverAccessDeniedHandler: ServerAccessDeniedHandler
): SecurityWebFilterChain {
http
.addFilterAt(userAuthenticationWebFilter, SecurityWebFiltersOrder.AUTHENTICATION)
.exceptionHandling()
.authenticationEntryPoint(userServerAuthenticationEntryPoint)
.and()
.authorizeExchange()
.pathMatchers(GET, "/sign-in").permitAll()
.pathMatchers("/authentication/**").permitAll()
.pathMatchers(GET, "/landing").hasAnyAuthority("USER", "ADMIN")
.pathMatchers("/user-api/**").hasAnyAuthority("USER", "ADMIN")
http
.addFilterAt(deviceAuthenticationWebFilter, SecurityWebFiltersOrder.AUTHENTICATION)
.exceptionHandling()
.authenticationEntryPoint(deviceServerAuthenticationEntryPoint)
.and()
.authorizeExchange()
.pathMatchers("/device-api/**").hasAuthority("DEVICE")
// GLOBAL
http
.httpBasic().disable()
.formLogin().disable()
.csrf().disable()
.cors().disable()
.securityContextRepository(serverSecurityContextRepository)
.authenticationManager(authenticationManager)
.exceptionHandling()
.accessDeniedHandler(serverAccessDeniedHandler)
.and()
.authorizeExchange()
.pathMatchers(GET, "/webjars/**").permitAll()
.pathMatchers(GET, "/assets/**").permitAll()
.anyExchange().authenticated()
return http.build()
}

It turns out the default ServerAuthenticationEntryPoint is a DelegatingServerAuthenticationEntryPoint, which enables you to configure via ServerWebExchangeMatchers which actual entry point is responsible for any given ServerWebExchange. See this comment.

Related

How combine two filter chains with spring security (jwt, basic auth)?

currently I have a security configuration which works fine. However I would like to optimize it.
The application defines an JwtFilter which checks requests for a token in the http header or cookie, if there is one its checked.
Now, for endpoints like actuator/metrics or swagger I defined a second filterchain with a securityMatcher (after spring boot 3 migration) to allow basic auth for those paths.
#Bean
#Order(62)
fun filterChain(http: HttpSecurity): SecurityFilterChain {
http
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeHttpRequests {
it.requestMatchers("/auth/**").permitAll()
it.requestMatchers(HttpMethod.GET, "/test").permitAll()
it.anyRequest().authenticated()
}
.addFilterBefore(
JwtFilter(jwtTokenService),
BasicAuthenticationFilter::class.java
)
.addFilterBefore(FilterChainExceptionHandler(handlerExceptionResolver), JwtFilter::class.java)
.exceptionHandling().authenticationEntryPoint(HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED))
return http.build()
}
#Bean
#Order(1)
fun specialPaths(http: HttpSecurity): SecurityFilterChain {
http
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.httpBasic()
.and()
.securityMatcher("/actuator/**", "/v3/api-docs/**", "/swagger/**")
.authorizeHttpRequests {
it.requestMatchers("/actuator/**").hasAnyRole("ADMIN")
it.requestMatchers("/v3/api-docs/**").hasAnyRole("ADMIN")
it.requestMatchers("/swagger/**").hasAnyRole("ADMIN")
}
return http.build()
}
I tried merging the configs in lots of ways, however it never works out like with the two separate chains.
Any tips or hints are greatly appreciated.

Authentication with spring and web flux

I have a question concerning spring and web flux.
I have a spring project with spring security and MVC as dependencies.
This application accepts requests and check authentication using the session cookie.
For all the requests starting with "/api/" a failed authentication results in a 401 response, so that can be intercepted by the frontend as such.
For all the requests different from "/api/**" a failed authentication results in the server returning a login page so that the user can login.
This is the SecuritConfig class:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.formLogin()
.and()
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.and()
.exceptionHandling()
.defaultAuthenticationEntryPointFor(new
HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED),
new AntPathRequestMatcher("/api/**"))
.and()
.cors();
}
}
Now, I am trying to achieve the same thing using web flux. With web flux the SecurityConfig is different, I can setup almost all the configs that I have in the old class but there is no equivalent for:
defaultAuthenticationEntryPointFor(new
HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED),
new AntPathRequestMatcher("/api/**"))
My new security config look like:
#Configuration
#EnableWebFluxSecurity
public class SecurityConfig {
#Bean
public SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
return http
.authorizeExchange()
.pathMatchers("/login/**")
.permitAll()
.anyExchange()
.authenticated()
.and()
.formLogin()
.and()
.csrf()
.disable()
.exceptionHandling()
.authenticationEntryPoint(new
HttpStatusServerEntryPoint(HttpStatus.UNAUTHORIZED))
.and()
.build();
}
}
But in this case I only get 401 for all the requests that fail authentication.
Does anybody know how to achieve the same behavior with web flux?
Thank you

Spring Security in Webflux

I have been trying to enable spring security in web flux, with my own custom authentication method. So far so good, but I am not able to allow certain URL patterns using permitall.
I have tried to create different beans of SecurityWebFilterChain, also tried with different config altogether, but nothing seems to work for me.
Here is my SecurityWebFilterChain
#Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
return http.csrf()
.disable()
.httpBasic()
.disable()
.formLogin()
.disable()
.logout()
.disable()
.authenticationManager(this.authenticationManager())
.securityContextRepository(this.securityContextRepository())
.authorizeExchange()
.pathMatchers("**/signal/health").permitAll()
.pathMatchers("**/order").permitAll()
.and()
.authorizeExchange()
.anyExchange()
.authenticated()
.and()
.build();
}
I have an internal health check system, which runs as soon as my application is up, so I want this to be allowed.
Moreover, I also want to allow another couple or URI, but the above config doesn't work for me.
Everything goes for authentication.
What am I doing wrong here?
I see a wrong and and authorizeExchange placed in between. Try using this:
#Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
return http.csrf().disable()
.httpBasic().disable()
.formLogin().disable()
.logout().disable()
.authenticationManager(this.authenticationManager())
.securityContextRepository(this.securityContextRepository())
.authorizeExchange()
.pathMatchers("**/signal/health","**/order").permitAll()
.anyExchange()
.authenticated()
.and().build();
}

Spring Security not intercepting correctly? [duplicate]

This question already has answers here:
Spring Security : Multiple HTTP Config not working
(2 answers)
Closed 3 years ago.
I have Spring Boot configuration which looks something like this:
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilterBefore( new Filter(), UsernamePasswordAuthenticationFilter.class)
.csrf().disable() // Disabled, cause enabling it will cause sessions
.headers()
.frameOptions()
.sameOrigin()
.addHeaderWriter(new XXssProtectionHeaderWriter())
.and()
.authorizeRequests()
.antMatchers("/app/**", "/rest/**").hasAuthority(DefaultPrivileges.ACCESS_TASK)
.anyRequest().permitAll();
My understanding was only the requests which start with /app or /rest will be intercepted by my custom filter but it turns out the requests to the root (http://localhost:8080/context/) are also intercepted.
I have multiple configurations for Spring Security the other configuration looks like this:
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.csrf().disable();
if (taskAppProperties.isRestEnabled()) {
if (restAppProperties.isVerifyRestApiPrivilege()) {
http
.antMatcher("/*-api/**")
.authorizeRequests()
.antMatchers("/*-api/**").hasAuthority(DefaultPrivileges.ACCESS_REST_API)
.and()
.httpBasic();
} else {
http
.antMatcher("/*-api/**")
.authorizeRequests()
.antMatchers("/*-api/**").authenticated()
.and()
.httpBasic();
}
} else {
http
.antMatcher("/*-api/**")
.authorizeRequests()
.antMatchers("/*-api/**").denyAll();
}
Can anyone help?
I realize this is a bit confusing, but there are actually two antMatchers methods, one that branches from authorizedRequests and another that branches from requestMatchers.
Let's look at the following declaration:
http
.requestMatchers()
.antMatchers("/app/**", "/api/**")
.and()
.authorizeRequests()
.antMatchers("...").authenticated()
...
The requestMatchers() DSL is where you describe the endpoints that matter to that instance of the Spring Security filter chain. So, this filter chain will only engage for URIs that start with /app or /api.
Let's take a look at another one:
http
.authorizeRequests()
.antMatchers("/app/**", "/api/**")
.authenticated();
While this may appear to be doing the same thing, it isn't. That's because you are calling the antMatchers method that belongs to authorizeRequests().
This is why indentation is important with the Spring Security DSL. Because there's a hierarchy in the DSL, then you want to indent, just like you want to indent inside your if statements.
In Spring Security 5.2, this is simplified a bit with the new lambda DSL:
http
.requestMatchers(r -> r.antMatchers("/app/**", "/api/**"))
.authorizeRequests(a -> a.antMatchers("...").authenticated());
HttpSecurity.authorizeRequests - returns ExpressionInterceptUrlRegistry where we are setting Matchers and Roles condition, which will be added using method ExpressionInterceptUrlRegistry.getRegistry and if you check other usage of this method only at permitAll stub where actual authentication happens.
The filter we add using HttpSecurity.addFilterBefore will not check any Request matching. If you need, you can do one more check in your custom filter to avoid other URIs
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilterAfter( new Filter() {
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = ((HttpServletRequest) request);
if(httpServletRequest.getRequestURI().startsWith("/app/") || httpServletRequest.getRequestURI().startsWith("/rest/")) {
// Do you secured filter computations
}
chain.doFilter(request, response);
}
#Override
public void destroy() {
}}, UsernamePasswordAuthenticationFilter.class)
.csrf()
.disable() // Disabled, cause enabling it will cause sessions
.headers()
.frameOptions()
.sameOrigin()
.addHeaderWriter(new XXssProtectionHeaderWriter())
.and()
.authorizeRequests()
.antMatchers("/app/**", "/rest/**")
.hasAuthority(DefaultPrivileges.ACCESS_TASK)
.anyRequest()
.permitAll();

Restrict access to Swagger UI

I have swagger UI working with spring-boot. I have a stateless authentication setup for my spring rest api which is restricted based on roles for every api path.
However, I am not sure how can i put <server_url>/swagger-ui.html behind Basic authentication.
UPDATE
I have following websecurity configured via WebSecurityConfig
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.csrf().disable()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
.antMatchers("/sysadmin/**").hasRole("SYSADMIN")
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/siteadmin/**").hasRole("SITEADMIN")
.antMatchers("/api/**").hasRole("USER")
.anyRequest().permitAll();
// Custom JWT based security filter
httpSecurity
.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
}
One suggestion without knowing more about your configuration is from this SO question.
https://stackoverflow.com/a/24920752/1499549
With your updated question details here is an example of what you can add:
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.csrf().disable()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
.antMatchers("/sysadmin/**").hasRole("SYSADMIN")
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/siteadmin/**").hasRole("SITEADMIN")
.antMatchers("/api/**").hasRole("USER")
// add the specific swagger page to the security
.antMatchers("/swagger-ui.html").hasRole("USER")
.anyRequest().permitAll();
// Custom JWT based security filter
httpSecurity
.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
}
The problem with this is it only protects the Swagger UI page and not the API specification which is loaded as a .json file from that UI page.
A better approach is to put the swagger files under a path so that you can just add antMatchers("/swagger/**").hasRole("USER")
A bit late to answer. I carried out a small POC to execute the same. I am using Keycloak and Spring Security. Below is my configuration
http
.antMatcher("/**").authorizeRequests()
.antMatchers("/swagger-resources/**","/swagger-ui.html**","/swagger-ui/").hasRole("admin")
.anyRequest().authenticated()
.and()
.exceptionHandling()
.accessDeniedHandler(new AccessDeniedHandlerImpl())
.defaultAuthenticationEntryPointFor(authenticationEntryPoint(), new CustomRequestMatcher(AUTH_LIST))
.and()
.httpBasic()
.authenticationEntryPoint(restAuthenticationEntryPoint)
.and()
.csrf().disable()
.logout()
.logoutUrl("/logout")
.invalidateHttpSession(true)
.clearAuthentication(true)
.addLogoutHandler(keycloakLogoutHandler());
I have a working example here

Resources