Reputation: 379
I am trying to setup multiple security configurations that will use different SecurityApiKeyFilter
classes based on the pathMatchers
, for now I only got 2. One which works for all URLs and one which works only on a URL that contains admin
. Initially, you are set as a guest and after that, we will try to authorize you based on ApiKey. However, I am not really able to get it to reach the 2nd SecurityWebFilterChain
configuration. Even though the pathMatcher
is set as so.
@Bean
@Order(1)
public SecurityWebFilterChain securitygWebFilterChain(ServerHttpSecurity http,
ClientService clientService) {
SecurityWebFilterChain filterChain = http.authorizeExchange()
.pathMatchers(HttpMethod.GET, "/").permitAll()
.pathMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.pathMatchers("/**").permitAll()
.anyExchange().authenticated().and()
.anonymous().principal("guest").and()
.addFilterBefore(new SecurityApiKeyFilter(clientService), SecurityWebFiltersOrder.AUTHENTICATION)
.oauth2ResourceServer().jwt()
.jwtDecoder(new NimbusReactiveJwtDecoder("/.well-known/jwks.json"))
.and()
.and().build();
return filterChain;
}
@Bean
@Order(2)
public SecurityWebFilterChain sdkJsWebFilterChain(ServerHttpSecurity http,
ClientService clientService) {
SecurityWebFilterChain filterChain = http.authorizeExchange()
.pathMatchers(HttpMethod.OPTIONS, "**/admin/**").permitAll()
.pathMatchers("**/admin/**").permitAll()
.anyExchange().authenticated().and()
.anonymous().principal("guest").and()
.addFilterBefore(new Admin.SecurityApiKeyFilter(clientService),
SecurityWebFiltersOrder.AUTHENTICATION)
.oauth2ResourceServer().jwt()
.jwtDecoder(new NimbusReactiveJwtDecoder("/.well-known/jwks.json"))
.and()
.and().build();
return filterChain;
}
Thanks.
Upvotes: 16
Views: 25815
Reputation: 3801
if you want to totally separate path patterns from other security web filter chains, you can configure multiple SecurityWebFilterChain
s by using securityMatcher
like below.
public SecurityWebFilterChain stackSagaEndpoint(ServerHttpSecurity http) {
http.securityMatcher(new PathPatternParserServerWebExchangeMatcher("/specific -path/**"))
....
}
and the execution will depend upon your further configurations like ordering and global configurations.
Upvotes: 4
Reputation: 379
I have also found this to be very helpful, in case anyone is looking for another type of solution it's not in Java but you will get the gist.
class SecurityConfig {
@Bean
fun getReactiveAuthenticationManager(): ReactiveAuthenticationManager {
return ReactiveAuthenticationManager { authentication ->
// simply return the authentication assuming the authentication was already verified in the converter
Mono.justOrEmpty(authentication)
}
}
companion object {
const val ADMIN_RESOURCE_A = "ADMIN_RESOURCE_A"
const val ADMIN_RESOURCE_B = "ADMIN_RESOURCE_B"
}
@Bean
fun springSecurityFilterChain(http: ServerHttpSecurity,
@Qualifier("authFilterResourceA")
authFilterResourceA: AuthenticationWebFilter,
@Qualifier("authFilterResourceB")
authFilterResourceB: AuthenticationWebFilter): SecurityWebFilterChain {
//configure security for resource a
http
.addFilterAt(authFilterResourceA, SecurityWebFiltersOrder.AUTHENTICATION)
.authorizeExchange()
.pathMatchers("/resourceA/**")
.hasRole(ADMIN_RESOURCE_A)
//configure security for resource b
http
.addFilterAt(authFilterResourceB, SecurityWebFiltersOrder.AUTHENTICATION)
.authorizeExchange()
.pathMatchers("/resourceB/**")
.hasRole(ADMIN_RESOURCE_B)
// global config
http
.httpBasic()
.disable()
.formLogin()
.disable()
.csrf()
.disable()
.cors()
.disable()
.authorizeExchange()
.anyExchange()
.authenticated()
return http.build()
}
@Bean("authFilterResourceA")
fun authFilterResourceA(authManager: ReactiveAuthenticationManager): AuthenticationWebFilter {
val filter = AuthenticationWebFilter(authManager)
filter.setServerAuthenticationConverter {
// simplified dummy token conversion for keeping the example as simple as possible
Mono.justOrEmpty(it)
.map { it.request.headers.getFirst("X-Application-Authentication") ?: "" }
.filter { it == "Bearer tokenForA" }
.map {
val authentication = UsernamePasswordAuthenticationToken(
"userWithAccessRightsToA",
it,
listOf(SimpleGrantedAuthority("ROLE_$ADMIN_RESOURCE_A"))
)
logger.info { "Created authentication: $authentication" }
authentication as Authentication
}
}
return filter
}
@Bean("authFilterResourceB")
fun authFilterResourceB(authManager: ReactiveAuthenticationManager): AuthenticationWebFilter {
val filter = AuthenticationWebFilter(authManager)
filter.setServerAuthenticationConverter {
// simplified dummy token conversion for keeping the example as simple as possible
Mono.justOrEmpty(it)
.map { it.request.headers.getFirst("X-Application-Authentication") ?: "" }
.filter { it == "Bearer tokenForB" }
.map {
val authentication = UsernamePasswordAuthenticationToken(
"userWithAccessRightsToB",
it,
listOf(SimpleGrantedAuthority("ROLE_$ADMIN_RESOURCE_B"))
)
logger.info { "Created authentication: $authentication" }
authentication as Authentication
}
}
return filter
}
Upvotes: 0
Reputation: 16979
I guess it is the same behavoir for reactive applications as for servlet applications.
Your second security filter chain is not executed, because only the first matching security filter chain will be invoked, see 9.4. SecurityFilterChain:
9.4. SecurityFilterChain
[...]
In fact,
FilterChainProxy
can be used to determine whichSecurityFilterChain
should be used. This allows providing a totally separate configuration for different slices of your application.In the Multiple SecurityFilterChain Figure
FilterChainProxy
decides whichSecurityFilterChain
should be used. Only the firstSecurityFilterChain
that matches will be invoked. If a URL of/api/messages/
is requested, it will first match onSecurityFilterChain0
's pattern of/api/**
, so onlySecurityFilterChain0
will be invoked even though it also matches onSecurityFilterChainn
. If a URL of/messages/
is requested, it will not match onSecurityFilterChain0
's pattern of/api/**
, soFilterChainProxy
will continue trying eachSecurityFilterChain
. Assuming that no other,SecurityFilterChain
instances matchSecurityFilterChainn
will be invoked.
Upvotes: 20