Reactive-Spring-Security-5.1.3.RELEASE, multiple authorizations - spring-boot

We have some endpoints, that are secured and before to access them we're verifying that the jws is correctly. In order to do that, we've defined a SecurityContext that actually persist the Auth pojo and to manipulate it downstream into the controller. The SecurityWebFilterChain config looks like that:
#Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
return http.csrf().disable()
.formLogin().disable()
.logout().disable()
.httpBasic().disable()
.securityContextRepository(securityContext)
.authorizeExchange()
.anyExchange().authenticated()
.and()
.build();
}
The calls were internally made, and we just verified the jws token.
Right now some external clients need to integrate with us, and we need to verify a jwe token. The thing is, that somehow we need to tell spring-security to validate for the existent endpoints the jws and for the new one the jwe.
I tried by specifying multiple security matchers but it failed :( . Do you have any other suggestions ?

You can expose more than one bean. I recommend specifying an order:
#Bean
#Order(1)
public SecurityWebFilterChain first(ServerHttpSecurity http) {
http
.securityMatcher(...)
...
return http.build();
}
#Bean
#Order(2)
public SecurityWebFilterChain second(ServerHttpSecurity http) {
http
.securityMatcher(...)
...
return http.build();
}
As a side note, Spring Security does ship with support for verifying JWS tokens reactively, and you might be able to remove some boilerplate by using it.

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.

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 6 configuration with multiple security configs

I'm trying to setup my Spring Boot 3.0 / Spring Security 6 app with multiple security configs.
only /oauth/token should use/allow/enforce basic auth
all other endpoints will use/allow/enforce bearer auth
The issue I'm running into is that if I send a GET request to /test with the header Authorization: Basic xxx the basic auth filter is still picking it up.
This is what I have so far. The bearer filter isn't implemented yet, but for the sake of this question, let's assume all other endpoints should be wide open instead. How can I get them to bypass the basic auth filter if a user passes in basic auth header?
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(CsrfConfigurer::disable)
.authorizeHttpRequests()
.requestMatchers("/oauth/token").authenticated()
.anyRequest().permitAll()
.and()
.httpBasic(Customizer.withDefaults());
return http.build();
}
Like this one:
private static final String[] RESOURCE_ARGS = new String[]{
"/test/**"
};
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests()
.requestMatchers(RESOURCE_ARGS).permitAll();
http
.csrf(CsrfConfigurer::disable)
.authorizeHttpRequests()
.requestMatchers("/oauth/token").authenticated()
.anyRequest().permitAll()
.and()
.httpBasic(Customizer.withDefaults());
....
}

Spring Security 6.0 allows me to see the h2-console login page but doesn't allow me to go inside, how do I do?

I'm struggling to access my h2-console under the protection of Spring Security 6.0, here is the code
#Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorizeRequests -> authorizeRequests
.requestMatchers("/h2-console/**").authenticated()
.anyRequest().authenticated()
)
.formLogin(formLogin -> formLogin
.permitAll()
)
.csrf(csrf -> csrf
.ignoringRequestMatchers("/h2-console/**"))
.headers(headers -> headers
.frameOptions().sameOrigin());
return http.build();
}
I can see the h2-console login page though I'm not allowed to go inside.
similar code works well with Spring Security 5.7.5
#Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorizeRequests -> authorizeRequests
.antMatchers("/h2-console/**").authenticated()
.anyRequest().authenticated()
)
.formLogin(formLogin -> formLogin
.permitAll()
)
.csrf(csrf -> csrf
.ignoringAntMatchers("/h2-console/**"))
.headers(headers -> headers
.frameOptions().sameOrigin())
;
return http.build();
}
I also tried WebSecurityCustomizer, which doesn't work either.
#Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return (web) -> web.ignoring()
.requestMatchers("/h2-console/**");
}
Here is the debug log
Securing POST
/h2-console/login.do?jsessionid=aa31e312f86f5a876457524984cad7e0
Invalid CSRF token found for
http://127.0.0.1:8080/h2-console/login.do?jsessionid=aa31e312f86f5a876457524984cad7e0
Responding with 403 status code
What am I missing?
All the merits go to this github issue.
I'm just copying the solution over here to make it more convenient to find, as I experienced myself various h2 console problems (401, 403, ...) when I migrated my app from spring boot 2.7.x to 3.0.x (e.g. spring core 5.x to 6.x, and spring security 5.x to 6.x), and it took a little while to fall on that actual github issue and find that solution, which works perfectly fine (more details on why the antMatcher is still being used can be found in the linked github issue...):
#Configuration
public class SecurityConfiguration {
#Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers(AntPathRequestMatcher.antMatcher("/h2-console/**")).permitAll()
)
.headers(headers -> headers.frameOptions().disable())
.csrf(csrf -> csrf
.ignoringRequestMatchers(AntPathRequestMatcher.antMatcher("/h2-console/**")));
return http.build();
}
}
I traced the source codes from CsrfFilter.java, checked the comparison logic that happens inside. Only to find that it is comparing the provided ignoring URL /h2-console/** with the requesting URL,let's say it is /h2-console/login.do, from which the servlet name counterpart /h2-console/ gets removed, therefore comparing /h2-console/** with login.do instead, end up being evaluated to false.
I dont know what the new purpose would be for doing this, but set CSRF disabled could fix this problem.
Or you could manually put a custom matching logic instead of telling Spring nothing but a URL pattern:
http.csrf().ignoringRequestMatchers(new RequestMatcher() {
#Override
public boolean matches(HttpServletRequest request) {
String contextPath = request.getServletContext().getContextPath();
return request.getRequestURI().startsWith(contextPath + "/h2-console/");
}
});
I dont like lambda at all. I will update this answer if I know something new.
I had to disable csrf, frameOptions, and allow path through requestMatcher to make it all work. I also used toH2Console as it automatically provides correct matcher.
import static org.springframework.boot.autoconfigure.security.servlet.PathRequest.toH2Console;
#Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
return httpSecurity
.csrf()
.disable()
.headers().frameOptions().disable()
.and()
.authorizeHttpRequests()
.requestMatchers(toH2Console())
.permitAll()
...

Form based or Single REST Controller authentication

Currently, my configuration is using HTTP basic and issuing JWT to the client. Can someone please help to make it such a way that JWT is issued from a single REST controller?
Without using HTTP basic.
I just want to do JWT authentication in my application.
Your effort and support is highly appreciated.
#Bean
public SecurityFilterChain userFilterChain(HttpSecurity http) throws Exception {
http.cors().disable();
http.csrf().disable();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
http.authorizeHttpRequests().antMatchers("/token").permitAll().and()
.antMatcher("/**")
.authorizeHttpRequests(userauthz ->
userauthz.antMatchers("/myaccount").authenticated()
)
.authenticationProvider(userAuthProvider())
.httpBasic();
return http.build();
}

Resources