Skip to content

As a developer, I want test OAuth2ResourceServer-based authorization without authentication #17829

@michaldo

Description

@michaldo

Expected Behavior

Consider the following security config, taken from documentation

public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(authorize -> authorize
                .requestMatchers("/messages/**").access(hasScope("message:read"))
                .anyRequest().authenticated()
            )
            .oauth2ResourceServer(oauth2 -> oauth2
                .jwt(jwt -> jwt
                    .jwtAuthenticationConverter(myConverter())
                )
            );
        return http.build();
}

I expect the following snippet should work out of the box (or with minimal effort)

@WebMvcTest
...
mockMvc.perform(
  get("/messages/foo").with(jwt().jwt(builder -> builder.claim("message", "read").build())))
  .andExpect(status().isOk());

Current Behavior

  1. Vanilla

application.properties: spring.security.oauth2.resourceserver.jwt.issuer-uri= ${ISSUER_URI}

Caused by: org.springframework.util.PlaceholderResolutionException: Could not resolve placeholder 'ISSUER_URI' in value "${ISSUER_URI}"

  1. Vanilla + excludeAutoConfiguration = OAuth2ResourceServerAutoConfiguration.class

No qualifying bean of type 'org.springframework.security.oauth2.jwt.JwtDecoder' available
Context

  1. Vanulla + excludeAutoConfiguration = OAuth2ResourceServerAutoConfiguration.class + @MockitoBean JwtDecoder jwtDecoder;

BUILD SUCCESS

Context

Security is authentication and authorization.
Very often external service - here ISSUER_URI - is responsible for authentication.
But authorization - here "/messages/**").access(hasScope("message:read") - is completely under developer control.
That is a reason why developer wants test something he control and does not test something he is not responsible.

Although @WebMvcTest is Annotation that can be used for a Spring MVC test that focuses only on Spring MVC components., the annotation is enriched (if possible) with some default security.
It is (more or less) reasonable to extent scope of @WebMvcTest for testing authorization (controlled), but not for authentication (uncontrolled)

Unfortunately, in 2020 in spring-projects/spring-boot#19823 it was reported that OAuth2AuthorizedClientArgumentResolver is not automatically added to @WebMvcTest. Instead of adding the resolver, autoconfiguration for OAuth2Client was added and for OAuth2Resource also, although OAuth2Resource is not related to OAuth2AuthorizedClientArgumentResolver

Finally, a developer which want to test his authorization code with @WebMvcTest, must focus on tame emerging authentication.

In 2022 in #11784 (smaller number, later issue) it was reported that @WebMvcTest unnecessary awakes OAuth2Client authentication.

But it was too late. Phrase from Javadoc By default, tests annotated with @WebMvcTest will also auto-configure Spring Security closes a discussion. Fairly speaking, it should be told that @WebMvcTest awakes OAuth2Client to register OAuth2AuthorizedClientArgumentResolver.

Conclusion
In my opinion authentication OAuth2ResourceServerAutoConfiguration in @WebMvcTest is a mistake. The same applies to other autoconfigurations responsible for authentication.

  1. If someone wants test his authentication, he is be responsible for adding OAuth2ResourceServerAutoConfiguration to his WebMvcTest.
  2. If someone wants test his RestController, which consumes JwtAuthenticationToken or OAuth2User, he should be able to do this without taming emerging authentication
  3. If someone wants test his authorization, it should be simple and supported by spring-security-test. For OAuth2Resource, it could be something like
public class MockOAuth2ResourceAutoConfiguration {

    @Component
    @ConditionalOnClass(JwtDecoder.class)
    @ConditionalOnMissingBean(JwtDecoder.class)
    static class TestJwtDecoder implements JwtDecoder {
        @Override
        public Jwt decode(String token) throws JwtException {
            return null;
        }
    }
}

It seems overengineering, but analogous MockOAuth2ClientAutoConfiguration should register OAuth2AuthorizedClientArgumentResolver and probably more (I don't know OAuth2Client well)

If the enhancement is accepted, I may implement it.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions