I'm using Spring WebFlux with RouterFunction to define my API endpoints. I have Spring Security configured the following way:
@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 is declared like this:
@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() }))
And the handler itself:
@Component
class TestHandler(private val testService: TestService) {
fun test(request: ServerRequest): Mono<ServerResponse> {
return request.toMono()
.transform({ testResponse() })
}
The issue is that request.principal() is always null the same as the ReactiveSecurityContextHolder.getContext().block() result, however authentication configuration works correctly and authenticates user. Interesting point that declaring endpoint using annotated controllers principal is not null and shows correctly authenticated user.
@RestController
class TestController(private val testService: TestService) {
@GetMapping("/test")
fun test(principal: Principal): Mono<String> {
return testService.test().map { message -> message + " " + principal.name }
}
}
Is it misconfiguration or a bug? There are similar questions asked, maybe it's the same issue?
SAMPLE:
Repo - https://github.com/yyunikov/spring-boot-2-kotlin-starter/
Curl example:
curl -H "Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJqdGkiOiJhNjQyMjk0NC01ZDFkLTQxODItOGE2ZS1mZGM0NjEwYzhlNTYiLCJzdWIiOiJ5dW5pa292IiwiaWF0IjoxNTIzNzQyNzcxLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvIiwiZW1haWwiOiJ1c2VyQHl1bmlrb3YuY29tIiwicm9sZXMiOlsidXNlciJdfQ.RirM-t7GZnSDQIAn_J-thD1UAzdyxKmiR4ktA_BdlYZsFoYKzy5nj-1dBaG60o7M9FcfwFLAWEe4pc7DplDNjw" localhost:8080/test
flatMap, can you explain whyrequest.principal().block()returns null? Isn't blocking should wait for result to be available? - Yuriy YunikovReactiveSecurityContextHolder.getContext()still doesn't return a security object, why it's happening? - Yuriy Yunikov