Skip to content

In Test @AuthenticationPrincipal is null because ServerWebExchange is not wrapped #6598

@Dav1dde

Description

@Dav1dde

Summary

Using @WithMockUser or org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers#mockAuthentication directly in combination with @AuthenticationPrincipal in a WebFlux controller leaves the principal empty while things like @PreAuthorize('isAuthenticated()') work as expected.

Actual Behavior

Mocking the principal in a test does not work with @AuthenticaitonPrincipal.

Because my SecurityFilterChain does not match the request of the web test client (see below, only matches Requests that contain an Authorization: Basic header), the org.springframework.security.web.server.context.SecurityContextServerWebExchangeWebFilter gets never called (which is part of the SecurityFilterChain), meaning the request is never wrapped with a org.springframework.security.web.server.context.SecurityContextServerWebExchange.

org.springframework.security.web.reactive.result.method.annotation.AuthenticationPrincipalArgumentResolver#resolveArgument uses exchange.getPrincipal() to resolve the principal, which returns an empty Mono because the exchange is still the DefaultServerWebExchange (not security!).

Now the principal set in org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.MutatorFilter is not considered.

The org.springframework.security.access.prepost.PrePostAdviceReactiveMethodInterceptor uses org.springframework.security.core.context.ReactiveSecurityContextHolder#getContext which would work for the resolver as well.

Expected Behavior

The mocked principal should be resolved correctly for the @AuthenticationPrincipal argument.

The exchange should be wrapped correctly.

Configuration

WebTestClient (minimal)

        this.webTestClient = WebTestClient.bindToApplicationContext(context)
            .apply(springSecurity())
            .configureClient()
            .mutateWith(mockUser(someUser))

Security (minimal):

    @Configuration
    public static class BasicAuthSecurity {
        @Bean
        public SecurityWebFilterChain basicAuthSecurityWebFilterChain(ServerHttpSecurity http) {
            return http
                .securityMatcher(new BasicAuthWebExchangeMatcher())
                .authorizeExchange().anyExchange().permitAll().and()
                .headers().frameOptions().disable().and()
                .csrf().disable()
                .logout().disable()
                .httpBasic().securityContextRepository(NoOpServerSecurityContextRepository.getInstance()).and()
                .exceptionHandling()
                    .accessDeniedHandler(new HttpStatusServerAccessDeniedHandler(HttpStatus.UNAUTHORIZED))
                    .authenticationEntryPoint(new HttpStatusServerEntryPoint(HttpStatus.UNAUTHORIZED)).and()
                .build();
        }
    }
public class BasicAuthWebExchangeMatcher implements ServerWebExchangeMatcher {
    @Override
    public Mono<MatchResult> matches(ServerWebExchange exchange) {
        var header = exchange.getRequest().getHeaders().getFirst("Authorization");
        return StringUtils.startsWithIgnoreCase(header, "Basic")
            ? MatchResult.match() : MatchResult.notMatch();
    }
}

The important part here is the security matcher, which makes it so my Security chain does not match, nor does any other!

Version

Spring Security 5.1.4

Sample

Company code.

Metadata

Metadata

Assignees

Labels

in: webAn issue in web modules (web, webmvc)type: enhancementA general enhancement

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions