title: Spring Security - Principal is always null using RouterFunction
body: <p>I'm using Spring WebFlux with <code>RouterFunction</code> to define my API endpoints. I have Spring Security configured the following way:</p>
  
  <pre><code>@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
  }
  </code></pre>
  
  <p><code>RouterFunction</code> is declared like this:</p>
  
  <pre><code>@Bean
  fun apiRouter(testHandler: TestHandler,
                errorHandler: ErrorHandler): RouterFunction&lt;*&gt; = router {
      (accept(MediaType.APPLICATION_JSON_UTF8).nest {
          GET(TEST_PATH, testHandler::test )
      })
  }.andOther(route(RequestPredicates.all(), HandlerFunction { errorHandler.notFoundResponse() }))
  </code></pre>
  
  <p>And the handler itself:</p>
  
  <pre><code>@Component
  class TestHandler(private val testService: TestService) {
  
      fun test(request: ServerRequest): Mono&lt;ServerResponse&gt; {
          return request.toMono()
                  .transform({ testResponse() })
      }
  </code></pre>
  
  <p>The issue is that <code>request.principal()</code> is always null the same as the <code>ReactiveSecurityContextHolder.getContext().block()</code> result, however authentication configuration works correctly and authenticates user. Interesting point that declaring endpoint using annotated controllers principal is <em>not null</em> and shows correctly authenticated user.</p>
  
  <pre><code>@RestController
  class TestController(private val testService: TestService) {
  
      @GetMapping("/test")
      fun test(principal: Principal): Mono&lt;String&gt; {
          return testService.test().map { message -&gt; message + " " + principal.name }
      }
  }
  </code></pre>
  
  <p>Is it misconfiguration or a bug? There are similar questions asked, maybe it's the same issue?</p>
  
  <p><strong>SAMPLE</strong>:</p>
  
  <p>Repo - <a href="https://github.com/yyunikov/spring-boot-2-kotlin-starter/" rel="nofollow noreferrer">https://github.com/yyunikov/spring-boot-2-kotlin-starter/</a> </p>
  
  <p>Line - <a href="https://github.com/yyunikov/spring-boot-2-kotlin-starter/blob/master/api/src/main/kotlin/com/yunikov/api/test/TestHandler.kt#L18" rel="nofollow noreferrer">https://github.com/yyunikov/spring-boot-2-kotlin-starter/blob/master/api/src/main/kotlin/com/yunikov/api/test/TestHandler.kt#L18</a></p>
  
  <p>Curl example:</p>
  
  <pre><code>curl -H "Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJqdGkiOiJhNjQyMjk0NC01ZDFkLTQxODItOGE2ZS1mZGM0NjEwYzhlNTYiLCJzdWIiOiJ5dW5pa292IiwiaWF0IjoxNTIzNzQyNzcxLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvIiwiZW1haWwiOiJ1c2VyQHl1bmlrb3YuY29tIiwicm9sZXMiOlsidXNlciJdfQ.RirM-t7GZnSDQIAn_J-thD1UAzdyxKmiR4ktA_BdlYZsFoYKzy5nj-1dBaG60o7M9FcfwFLAWEe4pc7DplDNjw" localhost:8080/test
  </code></pre>