简体   繁体   中英

Adding multiple Spring Security configurations based on pathMatcher

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.

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 which SecurityFilterChain should be used. This allows providing a totally separate configuration for different slices of your application.

在此处输入图像描述

In the Multiple SecurityFilterChain Figure FilterChainProxy decides which SecurityFilterChain should be used. Only the first SecurityFilterChain that matches will be invoked. If a URL of /api/messages/ is requested, it will first match on SecurityFilterChain0 's pattern of /api/** , so only SecurityFilterChain0 will be invoked even though it also matches on SecurityFilterChainn . If a URL of /messages/ is requested, it will not match on SecurityFilterChain0 's pattern of /api/** , so FilterChainProxy will continue trying each SecurityFilterChain . Assuming that no other, SecurityFilterChain instances match SecurityFilterChainn will be invoked.

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
}

Source: https://github.com/jimonthebarn/spring-webflux-multiple-auth-mechanisms/blob/master/src/main/kotlin/localhost/playground/multi/auth/SecurityConfig.kt

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM