Spring Security - 使用 RouterFunction 的 Principal 始终为空

Posted

技术标签:

【中文标题】Spring Security - 使用 RouterFunction 的 Principal 始终为空【英文标题】:Spring Security - Principal is always null using RouterFunction 【发布时间】:2018-09-30 08:04:18 【问题描述】:

我正在使用带有RouterFunction 的 Spring WebFlux 来定义我的 API 端点。我已经通过以下方式配置了 Spring Security:

@Bean
fun security(httpSecurity: ServerHttpSecurity): SecurityWebFilterChain 
    return httpSecurity
            .authenticationManager(authenticationManager)
            // disable default security
                .httpBasic().disable()
                .formLogin().disable()
                .logout().disable()
                .csrf().disable()
            .addFilterAt(apiAuthenticationWebFilter(), SecurityWebFiltersOrder.AUTHENTICATION)
            .authorizeExchange()
                .pathMatchers("/app/health").permitAll()
                .pathMatchers("/app/info", "/app/loggers", "/app/metrics").hasAuthority(SecurityRole.SYSTEM)
                .anyExchange()
                .authenticated()
            .and()
                .exceptionHandling()
                .authenticationEntryPoint(jwtAuthenticationFailureHandler)
            .and()
            .build()


@Bean
fun securityContextRepository(): NoOpServerSecurityContextRepository 
    return NoOpServerSecurityContextRepository.getInstance()


private fun apiAuthenticationWebFilter(): AuthenticationWebFilter 
    val apiAuthenticationWebFilter = AuthenticationWebFilter(authenticationManager)
    apiAuthenticationWebFilter.setAuthenticationFailureHandler(ServerAuthenticationEntryPointFailureHandler(jwtAuthenticationFailureHandler))
    apiAuthenticationWebFilter.setAuthenticationConverter(jwtAuthenticationConverter)
    apiAuthenticationWebFilter.setRequiresAuthenticationMatcher(PathPatternParserServerWebExchangeMatcher("/**"))
    apiAuthenticationWebFilter.setSecurityContextRepository(securityContextRepository())
    return apiAuthenticationWebFilter

RouterFunction 声明如下:

@Bean
fun apiRouter(testHandler: TestHandler,
              errorHandler: ErrorHandler): RouterFunction<*> = router 
    (accept(MediaType.APPLICATION_JSON_UTF8).nest 
        GET(TEST_PATH, testHandler::test )
    )
.andOther(route(RequestPredicates.all(), HandlerFunction  errorHandler.notFoundResponse() ))

以及处理程序本身:

@Component
class TestHandler(private val testService: TestService) 

    fun test(request: ServerRequest): Mono<ServerResponse> 
        return request.toMono()
                .transform( testResponse() )
    

问题是request.principal() 始终为空,与ReactiveSecurityContextHolder.getContext().block() 结果相同,但是身份验证配置可以正常工作并验证用户。有趣的一点是,使用带注释的控制器主体声明端点是 not null 并显示正确经过身份验证的用户。

@RestController
class TestController(private val testService: TestService) 

    @GetMapping("/test")
    fun test(principal: Principal): Mono<String> 
        return testService.test().map  message -> message + " " + principal.name 
    

是配置错误还是错误?有类似的问题问,也许是同一个问题?

样本

回购 - https://github.com/yyunikov/spring-boot-2-kotlin-starter/

线路 - https://github.com/yyunikov/spring-boot-2-kotlin-starter/blob/master/api/src/main/kotlin/com/yunikov/api/test/TestHandler.kt#L18

卷曲示例:

curl -H "Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJqdGkiOiJhNjQyMjk0NC01ZDFkLTQxODItOGE2ZS1mZGM0NjEwYzhlNTYiLCJzdWIiOiJ5dW5pa292IiwiaWF0IjoxNTIzNzQyNzcxLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvIiwiZW1haWwiOiJ1c2VyQHl1bmlrb3YuY29tIiwicm9sZXMiOlsidXNlciJdfQ.RirM-t7GZnSDQIAn_J-thD1UAzdyxKmiR4ktA_BdlYZsFoYKzy5nj-1dBaG60o7M9FcfwFLAWEe4pc7DplDNjw" localhost:8080/test

【问题讨论】:

这可能是 ***.com/questions/48156572/… 的副本吗? @dmendezg 确实,它似乎通过flatMap 工作,你能解释为什么request.principal().block() 返回null 吗?阻塞不应该等待结果可用吗? 但是平面映射ReactiveSecurityContextHolder.getContext() 仍然没有返回安全对象,为什么会这样? 【参考方案1】:

内部实现基于委托者,因此如果尚未解决,则返回 null(此行为现在导致异常)

这是一个如何获得两者的示例。

fun secure(serverRequest: ServerRequest): Mono<ServerResponse> 
    return Mono.zip(
        serverRequest.principal(),
        ReactiveSecurityContextHolder.getContext()
    )
        .flatMap 
            val principal = it.t1 // Not null
            Assert.notNull(principal)
            val securityContext = it.t2 // Not null
            Assert.notNull(securityContext)
            ServerResponse.ok().body(fromObject("Hello $principal!"))
        

注意使用block()/blockFirst()/blockLast() 会导致异常为IllegalStateException

【讨论】:

以上是关于Spring Security - 使用 RouterFunction 的 Principal 始终为空的主要内容,如果未能解决你的问题,请参考以下文章

在使用 Oauth、SAML 和 spring-security 的多租户的情况下从 spring-security.xml 中获取错误

Spring security:Spring security 如何在 SessionRegistry 中注册新会话?

未调用 Spring Security j_spring_security_check

使用 spring-session 和 spring-cloud-security 时,OAuth2ClientContext (spring-security-oauth2) 不会保留在 Redis

如何使用 Spring-Security 3 和 Hibernate 4 将 spring security xml 配置 hibernate 转换为 java config

Spring-Security:升级到 Spring-Security 4.1 后,用户名发送为空以进行登录