Require multiple authorities in Spring ServerHTTPSecurity - spring

I'm using Spring Security with Spring Cloud Gateway and need to configure routes that require multiple authorities. So far I'm only able to specify an individual authority per route:
#Order(Ordered.HIGHEST_PRECEDENCE)
#Bean
public SecurityWebFilterChain apiHttpSecurity(ServerHttpSecurity http) {
http.securityMatcher(new PathPatternParserServerWebExchangeMatcher("/api/**"))
.authorizeExchange(exchanges -> exchanges
.pathMatchers("/api/developer/**").hasAuthority("Developer")
.pathMatchers("/api/admin/**").hasAuthority("Admin")
.anyExchange().authenticated())
.oauth2ResourceServer(oauth2 -> oauth2.jwt(jwt -> jwt.jwtAuthenticationConverter(grantedAuthoritiesExtractor())));
return http.build();
}
How can I extend this so certain routes require more than one authority? Such as:
.pathMatchers("/api/example/**/).hasAuthority("Developer").hasAuthority("SeniorDev")
The .hasAnyAuthority("Developer", "SeniorDev") method exists and works as an OR statement but I don't see a method available for an AND statement.

you can just use below code :
.antMatchers("/api/example/**").access("hasAuthority('developer') and hasAuthority('admin')")
UPDATED**********
try this solution for ServerHttpSecurity
.pathMatchers("/api/example/**").access((mono, context) -> mono
.map(auth -> auth.getAuthorities().stream()
.filter(e -> (e.getAuthority().equals("ADMIN") && e.getAuthority().equals("DEVELOPER"))).count() > 0)
.map(AuthorizationDecision::new))

Related

What is the purpose of withDefaults() is spring

As per spring docs it says:
Returns a Customizer that does not alter the input argument.
But what does this exactly mean?
For e.g. what is the result if I use it like this:
#EnableWebSecurity
#Configuration
public class SecurityConfiguration {
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeHttpRequests((authz) -> authz.anyRequest().authenticated())
.httpBasic(withDefaults());
return http.build();
}
}
According to java doc, we have
public HttpSecurity httpBasic(Customizer<HttpBasicConfigurer<HttpSecurity>> httpBasicCustomizer) throws Exception {
httpBasicCustomizer.customize((HttpBasicConfigurer)this.getOrApply(new HttpBasicConfigurer()));
return this;
}
The parameter is of type Customizer<HttpBasicConfigurer<HttpSecurity>> which can be used as a lambda function to pass the changes that you want to be applied in the configurer that you provide to the httpBasic method. This method also returns the built HttpSecurity so the configurer is already applied, when the httpBasic(....) method ends.
A relative to your example let's name it Example 1
httpSecurity.httpBasic(httpSecurityHttpBasicConfigurer -> {
httpSecurityHttpBasicConfigurer.realmName("My Realm");
httpSecurityHttpBasicConfigurer.authenticationEntryPoint(new YourAuthEntryClass());
})
.authorizeRequests().and().csrf().disable().authorizeHttpRequests((authz) -> authz.anyRequest().authenticated());
So the configurer is going to apply the realmName and authenticationEntryPoint to the httpSecurity via the lambda function which you have provided.
In case you didn't want to make any modifications to the httpSecurity inside the httpBasic method you could as well have done the
httpSecurity.httpBasic(httpSecurityHttpBasicConfigurer -> {} )
.authorizeRequests().and().csrf().disable().authorizeHttpRequests((authz) -> authz.anyRequest().authenticated());
And Spring just to avoid writting this nonsese httpSecurityHttpBasicConfigurer -> {} as parameter has also gave you this with static withDefaults method in the functional interface Customizer. Keep in mind that this Customizer is just a generic Interface and will be used in other places as well not just here.
#FunctionalInterface
public interface Customizer<T> {
void customize(T t);
static <T> Customizer<T> withDefaults() {
return (t) -> {
};
}
}
So to avoid the
httpSecurity.httpBasic(httpSecurityHttpBasicConfigurer -> {} )....
you can as well write
httpSecurity.httpBasic(Customizer.withDefaults())....
meaning no configuration will be applied inside the httpBasic method in the httpSecurity object.
Keep in mind however,
that you also have available another method from Java Docs
public HttpBasicConfigurer<HttpSecurity> httpBasic() throws Exception {
return (HttpBasicConfigurer)this.getOrApply(new HttpBasicConfigurer());
}
which could be used as well and this doesn't return the modified httpSecurity object but an HttpBasicConfigurer instead which could be written as to modify the httpSecurity using the builder pattern.
So the Example 1 could now be written as
httpSecurity.httpBasic()
.realmName("My Realm")
.authenticationEntryPoint(new YourAuthEntryClass())
.and().authorizeRequests().and().csrf().disable()
.authorizeHttpRequests((authz) -> authz.anyRequest().authenticated());
And in case you didn't want to apply any basic http configuration changes to the httpSecurity you could just skip the methods realmName and authenticationEntryPoint from the builder pattern, and it would have provide you again with the default basic configurations for httpSecurity
httpSecurity.httpBasic()
.and()
.authorizeRequests().and().csrf().disable()
.authorizeHttpRequests((authz) -> authz.anyRequest().authenticated());
which would be exactly the same with the version of
httpSecurity.httpBasic(Customizer.withDefaults())
.authorizeRequests().and().csrf().disable()
.authorizeHttpRequests((authz) -> authz.anyRequest().authenticated());
use default setting.
for ex,
whom to authorize
how to authorize

Spring security: avoid authorization and authentication for certain paths

This is my related spring security configuration:
#Bean
SecurityWebFilterChain springSecurityFilterChain(
ServerHttpSecurity http,
AuthenticationWebFilter authenticationWebFilter
) {
return http
.httpBasic(HttpBasicSpec::disable)
.csrf(CsrfSpec::disable)
.formLogin(FormLoginSpec::disable)
.anonymous(AnonymousSpec::disable)
.logout(LogoutSpec::disable)
.authorizeExchange((authorize) -> authorize
.pathMatchers("/actuator/**").permitAll()
.pathMatchers("/login/**").permitAll()
.anyExchange().authenticated()
)
.addFilterAt(authenticationWebFilter, SecurityWebFiltersOrder.AUTHENTICATION)
.build();
}
#Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return (web) -> web.ignoring().antMatchers("/actuator/**", "/login/**");
}
As you can see, I'm setting an AuthenticationWebFilter at SecurityWebFiltersOrder.AUTHENTICATION level.
I'm also avoiding to authorize "/actuator/**" and "/login/**" paths and I'm avoiding authentication for using WebSecurityCustomizer.
However, when I'm trying to reach above paths, I see authentication process is launched for above paths since, authenticationFilter is reached at code.
How could avoid authorization and authentication only for "/actuator/**" and "/login/**"?
EDIT:
I've took a look on this question, but its approach is what I'm using exactly with WebSecurityCustomizer...

Can mutual auth and x509 co exist in same spring boot application?

I'm running two jvms. One with webapp jwt auth and another one for mutual auth. It creates lot of overhead to the hosting machine? Is it possible to auth different paths with different auth mode in spring?
Yes, you can use multiple filter chains, one for each path:
#Bean
#Order(0)
SecurityFilterChain jwtPaths(HttpSecurity http) throws Exception {
http
.requestMatchers((requests) -> requests.mvcMatchers("/jwt-paths/**"))
// configuration for paths that use JWT auth
return http.build();
}
#Bean
#Order(1)
SecurityFilterChain x509Paths(HttpSecurity http) throws Exception {
http
.requestMatchers((requests) -> requests.mvcMatchers("/x509Paths/**"))
// configuration for paths that use X.509 auth
return http.build();
}

How to get ServerWebExchange object in SecurityWebFilterChain bean

Hello Spring WebFlux community,
I have implemented x509 based authentication in spring webflux security bean using below code:
#Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
ReactiveAuthenticationManager authMan = auth0 -> {
..//lambda logic of setAuthenticated true, HERE I NEED TO ACCESS CURRENT REQUEST HEADERS VALUE BEFORE SETTING IT TO TRUE
};
http.x509(x509 -> x509
.principalExtractor(myx509PrincipalExtractor())
.authenticationManager(authMan ))
.authorizeExchange(exchanges ->
exchanges.anyExchange().authenticated());
return http.build();
}
Now, please observe the comment part inside auth0 lambda, there I need to access header values of current request. How can I get that ?

Spring Boot 2.0 web flux custom authentication -- how to?

There are plenty of examples of minimal configurations of Spring Boot 2.0 security which compile or don't depending on which milestone or release candidate you try.
What is a minimal configuration that is not HTTP Basic, that will (1) let me access the HTTP request (headers, cookies, etc.) and also call my own authentication manager?
I would like to look at the headers and cookies, and decide from those who the user is, and whether or not the user is authenticated. How I do that should not matter to this answer -- the question is, what is the minimal Spring security config in order to allow me to hook in to the security infrastructure, so that my authentication is there in the reactive endpoints?
EDIT:
This works with Spring Boot 2.0.0.RC2, so my question could be, is this a correct way to introduce custom authentication into Spring Security?
#Configuration
#EnableWebFluxSecurity
public class SecurityConfiguration {
#Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
AuthenticationWebFilter authenticationFilter = new AuthenticationWebFilter(authentication -> {
authentication.setAuthenticated(true);
return Mono.just(authentication);
});
authenticationFilter.setAuthenticationConverter(serverWebExchange ->
Mono.just(new AbstractAuthenticationToken(new ArrayList<>()) {
#Override
public Object getCredentials() {
return null;
}
#Override
public Object getPrincipal() {
return "jim";
}
}));
return http.addFilterAt(authenticationFilter, SecurityWebFiltersOrder.FORM_LOGIN)
.authorizeExchange()
.anyExchange()
.authenticated()
.and()
.build();
}
}
You can imagine that in the converter, I am free to look into the request by way of serverWebExchange and inspect any headers or cookies I wish, and that later in the upper lambda (standing in for ReactiveAuthenticationManager) I can actually decide whether or not it should be authenticated.

Resources