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

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.

Related

Permit only specific HTTP operations in Spring Security

I have a Spring security config with a standard (?) filter chain configuration to allow some open endpoints of my api:
#Bean
fun filterChain(http: HttpSecurity): SecurityFilterChain {
http
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeHttpRequests() {
it.antMatchers("/auth/**").permitAll()
it.antMatchers("/info").permitAll()
}
.exceptionHandling().authenticationEntryPoint(HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED))
return http.build()
}
This works fine. However I am currently trying to allow only GET operations on "/info". Is this somehow achievable in this config?
I tried permitAll() and #PreAuthorize("SOME_ROLE") in the Controller. This is results in an AccessDeniedException which is mapped to a HTTP 403 forbidden response which I do not want in this specific case. Not providing auth information to a protected API should result in a HTTP 401 response. I also could just define another path for the POST request, but this seems a bit clunky.
as M. Deinum posted in the comments the HTTPMethod can easily be specified in the antMatcher.
#Bean
fun filterChain(http: HttpSecurity): SecurityFilterChain {
http
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeHttpRequests() {
it.antMatchers("/auth/**").permitAll()
it.antMatchers(HttpMethod.GET, "/info")
}
.exceptionHandling().authenticationEntryPoint(HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED))
return http.build()
}

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 WebFlux Security - Is it possible to configure multiple ServerAuthenticationEntryPoints on a SecurityWebFilterChain for different resources

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.

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

Spring Boot Security - Multiple configurations

I'm working (and struggling a little bit) on an example using spring-boot with spring security.
My system is using a web app and also provide an REST-API, so i would like to have form based security (web) and basic auth (resp api).
As the spring documentation recommend (https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#multiple-httpsecurity), I need to create a multi http web security configuration.
The main code works, but if I use Postman for the test of my RestApi following use-case does not work.
All GET-requests to /restapi/ working without authentication (statuscode 200)
All POST-requests to /restapi/ without the BASIC Auth Header are working (statuscode 401)
All POST-requests to /restapi/ with a correct BASIC Auth Header are work (statuscode 200)
BUT all requests with a wrong BASIC Auth header (f.e. user1/1234567) are returning the HTML-Loginpage defined in the first WebSecurityConfigurerAdapter (FormWebSecurityConfigurerAdapter)
Does anyone has an idea - what is wrong with my configuration?
#EnableWebSecurity
public class MultiHttpSecurityConfig {
#Autowired
private static RestAuthenticationAccessDeniedHandler restAccessDeniedHandler;
#Autowired
public void configureAuth(AuthenticationManagerBuilder auth) throws Exception{
auth.inMemoryAuthentication()
.withUser("admin").password("{noop}12345678").roles("ADMIN").and()
.withUser("user").password("{noop}12345678").roles("USER");
}
#Configuration
#Order(1)
public static class RestWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/restapi/**")
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/restapi/**").permitAll()
.and()
.authorizeRequests().anyRequest().authenticated()
.and()
.httpBasic()
.and()
.csrf().disable()
.exceptionHandling().authenticationEntryPoint(new HttpStatusEntryPoint(UNAUTHORIZED))
.and()
.exceptionHandling().accessDeniedHandler(restAccessDeniedHandler) ;
}
}
/*
Ensures that any request to our application requires the user to be authenticated (execpt home page)
Requests matched against "/css/**", "/img/**", "/js/**", "/index.html", "/" are fully accessible
Allows users to authenticate with HTTP Form Based authentication
Configure logout with redirect to homepage
*/
#Configuration
public static class FormWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/css/**", "/img/**", "/js/**", "/index.html", "/").permitAll()
.and()
.authorizeRequests().anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/index.html")
.permitAll();
}
}
}
I know it is a question from some time ago but I still want to share the answer for people who are struggling with this issue.
After a lot of searching I found out that the /error endpoint in spring boot 2.x is now secured by default. What I mean to say is in the past the /error was a endpoint what had no security at all (or didn't exist). The solution to this issue is quite straight forward.
antMatchers('/error').permitAll()
within your web security adapter configuration(s).
What happens if you don't do this, the security will check the endpoint against your configuration and if it cannot find this endpoint (/error) it will redirect to the standard login form, hence the 302.

Resources