Summary

I recently updated a project from a pre-release of spring boot 2 to the most current 2.0.5 build. I am no longer able to retrieve the username of the currently authenticated user for use in calls.

I have tried both via the Principal in the ServerRequest, and the Authentication in the ReactiveSecurityContext.

Authentication itself is successful, with the automatically build in login form and my MapReactiveUserDetailsService.

Actual Behavior

User name is empty

Expected Behavior

User name matches that of authenticated user.

Configuration

I am using Method Security.

    @Bean
    fun securityWebFilterChain(
            http: ServerHttpSecurity): SecurityWebFilterChain {
        return http
                .authorizeExchange()
                .anyExchange().authenticated()
                .and()
                .formLogin()
                .and()
                .build()
    }

    @Bean
    fun userDetailsService(
            encoder: BCryptPasswordEncoder,
            userRepository: UserRepository,
            environment: Environment): MapReactiveUserDetailsService {
        var users = userRepository.findAll().map {
                        User.withUsername(it.username)
                    .password(it.password)
                    .roles("USER")
                    .build()
        }.toIterable().toList()

        if(users.isEmpty()) {
            val adminPassword = environment.getProperty("news.admin.password", String::class.java)

            if(adminPassword != null) {
                val admin = User.withUsername("Admin")
                        .password(encoder.encode(adminPassword))
                        .roles("USER", "ADMIN")
                        .build()

                users += admin
            }
        }
        return MapReactiveUserDetailsService(users)
    }

Version

Most current version included in Spring Boot 2.0.5.RELEASE

Sample

    @PreAuthorize("isAuthenticated()")
    fun getUserNews(serverRequest: ServerRequest): Flux<ViewModel> {
//        val username = ReactiveSecurityContextHolder.getContext().map { it.authentication }.map { it.name }
//                .switchIfEmpty(Mono.error(IllegalStateException("Unable to retrieve username")))

        val username = serverRequest.principal().map { it.name }
                .switchIfEmpty(Mono.error(IllegalStateException("Unable to retrieve username")))

        val user = userRepo.findByUsername(username)


        return articleRepo
                .findAll()
                .zipWith(user.repeat())
                .filter { tup -> !tup.t2.ignored.contains(tup.t1.id)}
                .sort { a1, a2 -> a1.t1.time.compareTo(a2.t1.time) }
                .map { it.t1.toViewModel() }
    }

Comment From: rwinch

Thanks for the report. I don't see an issue looking at your code, nor am I able to reproduce the issue. Can you provide a complete sample that reproduces this?

Comment From: dillius

Let's make this even weirder:

If I switch the reactor .map { } for .flatMap { }, it works.

I would have to guess this is some quality of the reactor processing; this fix was discussed as having something to do with the availability of the principal being dependent on a delegate.