在Spring Security 5Webflux中强制给定路径的ServerAuthenticationFailureHandler

问题描述

我有两条路径/foo/bar。对于/foo,我有一个自定义的身份验证机制。在/bar上,我进行了Basic认证。

除一种情况外,此设置均可正常运行。如果我没有在AUTHORIZATION中传递/foo标头,则将启动基本身份验证,而不是MyAuthenticationFailureHandler

是否可以为给定路径设置MyAuthenticationFailureHandler?还是我用SecurityWebFiltersOrder弄乱了东西?

我的完整安全性配置:

bean<MyReactiveUserDetailsService>()
bean<MyReactiveAuthenticationManager>()
bean {
    ref<ServerHttpSecurity>()
        .securityMatcher {
            if (it.request.path.value().contains("/bar")) {
                ServerWebExchangeMatcher.MatchResult.match()
            } else {
                ServerWebExchangeMatcher.MatchResult.notMatch()
            }
        }
        .formLogin().disable()
        .csrf().disable()
        .logout().disable()
        .httpBasic()
        .and()
        .authorizeExchange()
        .pathMatchers("/bar/**")
        .hasRole("ADMIN")
        .anyExchange().permitAll()
        .and()
        .build()
}

bean {
    ref<ServerHttpSecurity>()
        .securityMatcher {
            if (it.request.path.value().contains("/bar")) {
                ServerWebExchangeMatcher.MatchResult.notMatch()
            } else {
                ServerWebExchangeMatcher.MatchResult.match()
            }
        }
        .httpBasic().disable()
        .formLogin().disable()
        .csrf().disable()
        .logout().disable()
        .authorizeExchange()
        .pathMatchers(
            HttpMethod.POST,"/foo/**"
        ).hasRole(
            "ABRACADABRA"
        )
        .anyExchange().permitAll()
        .and()
        .addFilterAt(
            authenticationWebFilter(ref(),ref()),SecurityWebFiltersOrder.AUTHENTICATION
        )
        .build()
}
}

private fun authenticationWebFilter(
    reactiveAuthenticationManager: ReactiveAuthenticationManager,objectMapper: ObjectMapper
) =
    AuthenticationWebFilter(reactiveAuthenticationManager).apply {
        setServerAuthenticationConverter(MyAuthenticationConverter())
        setRequiresAuthenticationMatcher(
            ServerWebExchangeMatchers.pathMatchers(
                HttpMethod.POST,"/foo/**"
            )
        )
        setAuthenticationFailureHandler(MyAuthenticationFailureHandler(objectMapper))
    }

class MyAuthenticationConverter : ServerAuthenticationConverter {
    override fun convert(exchange: ServerWebExchange): Mono<Authentication> {
        val authHeader: String? = exchange.request.headers.getFirst(HttpHeaders.AUTHORIZATION)
        // ... 
        return when {
            isValid(authHeader,...) -> {
                Mono.just(
                    UsernamePasswordAuthenticationToken(principal,credentials)
                )
            }
            else -> Mono.empty()
        }
    }
}

class MyAuthenticationFailureHandler(private val objectMapper: ObjectMapper) : ServerAuthenticationFailureHandler {
    override fun onAuthenticationFailure(
        webFilterExchange: WebFilterExchange,exception: AuthenticationException?
    ): Mono<Void> {
        val response = webFilterExchange.exchange.response
        response.apply {
            statusCode = HttpStatus.OK
            headers.contentType = MediaType.APPLICATION_JSON
            headers.set(HttpHeaders.WARNING,"""199 warning "Invalid token"""")
        }

        return response.writeWith(
            Flux.just(
                DefaultDataBufferFactory().wrap(
                    objectMapper.writeValueAsBytes(
                        MyDto(
                            // ...
                        ).toSettingResponse()
                    )
                )
            )
        )
    }
}

解决方法

您可以使用 AuthenticationWebFilter 设置自定义 ServerAuthenticationFailureHandler

@Bean
public SecurityWebFilterChain securitygWebFilterChain(ServerHttpSecurity http) {
            AuthenticationWebFilter authenticationWebFilter = new AuthenticationWebFilter(authenticationManager);
            authenticationWebFilter.setAuthenticationFailureHandler(myAuthenticationFailureHandler) 
     return http.authorizeExchange()
                    .pathMatchers("/user/signup")
                    .permitAll().pathMatchers("/user/login")
                    .permitAll()
                    .anyExchange().authenticated()
                    .and()
                    .securityContextRepository(NoOpServerSecurityContextRepository.getInstance())
                    .addFilterAt(authenticationWebFilter,SecurityWebFiltersOrder.AUTHENTICATION)
                    .httpBasic().disable()
                    .csrf().disable()
                    .formLogin().disable()
                    .logout().disable()
                    .build();
        }

相关问答

依赖报错 idea导入项目后依赖报错,解决方案:https://blog....
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下...
错误1:gradle项目控制台输出为乱码 # 解决方案:https://bl...
错误还原:在查询的过程中,传入的workType为0时,该条件不起...
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct...