Affects: \5.2.9 Affects: \security: 5.3.4


I have the following code, where testBWithAuth throws a null pointer exception.


@SpringBootTest
@ContextConfiguration(classes = Configuration.class)
public class ApplicationTest {

  private static String signingKey = "signingKey";

  @TestConfiguration
  @EnableResourceServer
  public static class Configuration extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
      http.cors();
      http.authorizeRequests().anyRequest().authenticated();
      http.sessionManagement().sessionCreationPolicy(STATELESS);
    }


    @Bean
    @Primary
    public DefaultTokenServices tokenServices() {
      DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
      defaultTokenServices.setTokenStore(tokenStore());
      defaultTokenServices.setSupportRefreshToken(true);
      return defaultTokenServices;
    }

    @Bean
    TokenStore tokenStore() {
      return new JwtTokenStore(accessTokenConverter());
    }

    @Bean
    JwtAccessTokenConverter accessTokenConverter() {
      final JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
      converter.setSigningKey(signingKey);
      return converter;
    }


    @Bean
    RouterFunction<ServerResponse> routerFunction() {
      final Builder routerBuilder = route();

      routerBuilder
        .GET("a", x -> ServerResponse.ok().body("A"))
        .GET("b", x -> ServerResponse.ok().body("B"))
      ;

      return routerBuilder.build();
    }
  }

  MockMvc mockMvc;

  @Autowired
  WebApplicationContext context;


  public static String createToken() {

    final JwtBuilder jwtBuilder = Jwts.builder();
    String token = jwtBuilder
      .claim("user_name", "123")
      .signWith(
        SignatureAlgorithm.HS256, Base64.getEncoder().encodeToString(signingKey.getBytes()))
      .compact();
    return String.format("Bearer %1$s", token);
  }

  @Test
  void testAWithAuth() throws Exception {
    mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
      .apply(springSecurity()).build();

    mockMvc.perform(get("/a")
      .header("Authorization", createToken())
    ).andExpect(status().isOk());
  }

  @Test
  void testBWithAuth() throws Exception {
    mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
      .apply(springSecurity())
      .build();

    mockMvc.perform(get("/b")
      .header("Authorization", createToken())
    )
      .andExpect(status().isOk());
  }

  @Test
  void testAWithoutAuth() throws Exception {
    mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build();

    mockMvc.perform(get("/a")).andExpect(status().isOk());
  }

  @Test
  void testBWithoutAuth() throws Exception {
    mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build();

    mockMvc.perform(get("/b")).andExpect(status().isOk());
  }

}

When adding more endpoints, they will also fail with a null pointer exception, only the first route can succeed.

I could trigger the NPE with the resource server only and not with "normal" JWT authentication. Also when disabling springSecurity (see tests), the code seems to work. However, when tracking down this issue I stumbled mainly over spring-framework classes and not over spring-security classes. This is why I am posting the issue here.

My current analysis is the following:

org.springframework.test.web.servlet.MockMvc.MVC_RESULT_ATTRIBUTE gets deleted before the handler method is called. Thus, after the execution of the handler function, when writing back the result, the search for this attribute results in null. Thus setting the value results in a NullPointerExceptino.

In fact, during execution of the springSecurity filter chain several attributes on the request are deleted since the restoreAttributes methods of RequestPredicates are unable to restore attributes in the map implementation of HandlerMappingIntrospector.RequestAttributeChangeIgnoringWrapper.

Currently, as a workaround I disable the CorsFilter in the FilterChainProxy.

Further Observations

The same setup worked for a standard setup with mappings via annotations only. However, when using WebMVC.fn those fail on tests as well. The issue does not seem to occur when starting the server itself (At least the mappings via annotations still seem to work).

Pointers to source code:

https://github.com/spring-projects/spring-framework/blob/fee8abfa5f54b9fa45f6f5a491bbbec1ac53b0a0/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/HandlerMappingIntrospector.java#L156-L164

getHandler calls eventually route of RouterFunctionBuilder

https://github.com/spring-projects/spring-framework/blob/fee8abfa5f54b9fa45f6f5a491bbbec1ac53b0a0/spring-webmvc/src/main/java/org/springframework/web/servlet/function/RouterFunctionBuilder.java#L359-L368

Which in turn will test a route and after the test restoreAttributes

https://github.com/spring-projects/spring-framework/blob/fee8abfa5f54b9fa45f6f5a491bbbec1ac53b0a0/spring-webmvc/src/main/java/org/springframework/web/servlet/function/RequestPredicates.java#L294-L299

putAll is implemented via put which uses setAttribute

https://github.com/spring-projects/spring-framework/blob/fee8abfa5f54b9fa45f6f5a491bbbec1ac53b0a0/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/HandlerMappingIntrospector.java#L233-L249

Comment From: snicoll

Thanks for the detailed analysis but we'd need that code in text to move in an actual project that we can run ourselves. Copying the code in a new project takes time and we can overlook something that you haven't shown in the description.

Comment From: spring-projects-issues

If you would like us to look at this issue, please provide the requested information. If the information is not provided within the next 7 days this issue will be closed.

Comment From: spring-projects-issues

Closing due to lack of requested feedback. If you would like us to look at this issue, please provide the requested information and we will re-open the issue.