-
Notifications
You must be signed in to change notification settings - Fork 5.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
mockJwt() WebTestClientConfigurer with MockMvcWebTestClient throws a NullPointerException. #9257
Comments
While there may be a wider picture to consider, the basic reason for the NPE is because |
UPDATED: Fixed workaround, demo CSRF support workaround, and provide link to complete example. @making Thanks for the report. We will look into a proper solution. In the meantime, you can work around it using @SpringBootTest
@AutoConfigureMockMvc
public class WebTestClientTest {
WebTestClient client;
@MockBean
// mock the JwtDecoder so that the jwks is not resolved since no AuthZ Server Setup
JwtDecoder jwtDecoder;
// Override the CsrfTokenRepository. Must explicitly wire CsrfTokenRepository Bean into DSL for this to work
@MockBean
CsrfTokenRepository csrfTokenRepository;
DefaultCsrfToken csrf = new DefaultCsrfToken("X-CSRF-TOKEN", "_csrf", "123");
@Autowired
void setMockMvc(MockMvc mockMvc) {
this.client = MockMvcWebTestClient.bindTo(mockMvc)
.build();
}
@BeforeEach
void setupCsrf() {
given(this.csrfTokenRepository.generateToken(any())).willReturn(csrf);
}
private Consumer<HttpHeaders> csrf() {
return (headers) -> headers.set(csrf.getHeaderName(), csrf.getToken());
}
@Test
void getWhenAuthenticated() {
TestSecurityContextHolder.setAuthentication(jwtAuthenticationToken());
client
.get()
.uri("/")
.exchange()
.expectStatus().isOk();
}
@Test
void getWhenNotAuthenticated() {
client
.get()
.uri("/")
.exchange()
.expectStatus().is4xxClientError();
}
@Test
void csrfWhenNoToken() {
TestSecurityContextHolder.setAuthentication(jwtAuthenticationToken());
client
.post()
.uri("/")
.exchange()
.expectStatus().is4xxClientError();
}
@Test
void csrfWhenValidToken() {
TestSecurityContextHolder.setAuthentication(jwtAuthenticationToken());
client
.post()
.uri("/")
.headers(csrf())
.exchange()
.expectStatus().isOk();
}
private static JwtAuthenticationToken jwtAuthenticationToken() {
return new JwtAuthenticationToken(jwt().build(), AuthorityUtils.createAuthorityList("SCOPE_message:read"));
}
public static Jwt.Builder jwt() {
// @formatter:off
return Jwt.withTokenValue("token")
.header("alg", "none")
.audience(Arrays.asList("https://audience.example.org"))
.expiresAt(Instant.MAX)
.issuedAt(Instant.MIN)
.issuer("https://issuer.example.org")
.jti("jti")
.notBefore(Instant.MIN)
.subject("mock-test-subject");
// @formatter:on
}
} |
@rstoyanchev Any ideas on how we can get Spring Security to integrate with the We need to be able to access the attribute on the |
I think there is some misunderstanding. As of 5.3 For the most part what can be done directly with For further reference the sections on |
This still doesn't work for me. |
Similar also happens in a test with
|
I'm experiencing the same behaviour than @membersound, do you have any workaround? |
@gursahibsahni Sorry the workaround I posted previously, should have removed the @soasada @membersound I've updated the sample above to demonstrate how to workaround CSRF test support not working too. @rstoyanchev Thanks for the reply. I'd like to figure out a way that Spring Security users can use |
The suggested workaround does not work with the current spring security versions (6.0.2), as csrf token handling has changed quite a bit. So far I was not able to create a working solution. |
as @eiswind mentioned, I have the same issue with spring boot 3 and security 6 this is my test class:
and this is the error I am getting:
none of the workarounds seem effective on it |
It's currently not possible to do this cleanly with The best workaround currently if you still want to use Some ideas
|
As a workaround I wrote a simple builder which can be used with the Edit: Have edited the builder as when I was doing testing I realised that what I typically wanted to use was the filters customised by Spring Boot in the MockMvcBuilder and not binding to the WebApplicationContext with springSecurity. package com.example;
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import org.springframework.http.HttpMethod;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.reactive.server.WebTestClientConfigurer;
import org.springframework.test.web.servlet.client.MockMvcWebTestClient;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.request.RequestPostProcessor;
import org.springframework.test.web.servlet.setup.AbstractMockMvcBuilder;
import org.springframework.web.context.WebApplicationContext;
public class DefaultMockMvcWebTestClient implements WebTestClient {
private final Function<MockHttpServletRequestBuilder, WebTestClient> builder;
private final List<RequestPostProcessor> requestPostProcessors = new ArrayList<>();
public DefaultMockMvcWebTestClient(WebApplicationContext context) {
this.builder = (requestBuilder) -> MockMvcWebTestClient.bindToApplicationContext(context)
.apply(springSecurity()).defaultRequest(requestBuilder);
}
public DefaultMockMvcWebTestClient(AbstractMockMvcBuilder<?> mockMvcBuilder) {
this.builder = (requestBuilder) -> MockMvcWebTestClient
.bindTo(mockMvcBuilder.defaultRequest(requestBuilder).build()).build();
}
public DefaultMockMvcWebTestClient(Function<MockHttpServletRequestBuilder, WebTestClient> builder) {
this.builder = builder;
}
public DefaultMockMvcWebTestClient(DefaultMockMvcWebTestClient copy) {
this.builder = copy.builder;
this.requestPostProcessors.addAll(copy.requestPostProcessors);
}
public DefaultMockMvcWebTestClient with(RequestPostProcessor requestPostProcessor) {
this.requestPostProcessors.add(requestPostProcessor);
return this;
}
public DefaultMockMvcWebTestClient mutateWith(RequestPostProcessor requestPostProcessor) {
DefaultMockMvcWebTestClient copy = new DefaultMockMvcWebTestClient(this);
return copy.with(requestPostProcessor);
}
public WebTestClient build() {
MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.get("/");
requestPostProcessors.stream().forEach(requestBuilder::with);
return this.builder.apply(requestBuilder);
}
@Override
public RequestHeadersUriSpec<?> get() {
return build().get();
}
@Override
public RequestHeadersUriSpec<?> head() {
return build().head();
}
@Override
public RequestBodyUriSpec post() {
return build().post();
}
@Override
public RequestBodyUriSpec put() {
return build().put();
}
@Override
public RequestBodyUriSpec patch() {
return build().patch();
}
@Override
public RequestHeadersUriSpec<?> delete() {
return build().delete();
}
@Override
public RequestHeadersUriSpec<?> options() {
return build().options();
}
@Override
public RequestBodyUriSpec method(HttpMethod method) {
return build().method(method);
}
@Override
public Builder mutate() {
return build().mutate();
}
@Override
public WebTestClient mutateWith(WebTestClientConfigurer configurer) {
return build().mutateWith(configurer);
}
} It can be used something like import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.oidcLogin;
public class MyTest {
DefaultMockMvcWebTestClient webTestClient;
@Autowired
public void setupClient(AbstractMockMvcBuilder<?> mockMvcBuilder) {
this.webTestClient = new DefaultMockMvcWebTestClient(mockMvcBuilder);
}
@Test
public void test() {
this.webTestClient.mutateWith(oidcLogin()).get() // ...
}
// ...
} |
@rwinch as per my spring-projects/spring-framework#30233 (review), I think we could do something with the |
Any target date for the final solution of this issue? @WebMvcTest(controllers = XxxController.class)
@WithMockUser
class XxxControllerTest {
@Autowired
private MockMvc mockMvc;
private WebTestClient webTestClient;
@BeforeEach
void setupWebClient() {
webTestClient = MockMvcWebTestClient
.bindTo(mockMvc)
.build()
// will encounter NPE if -> .mutateWith(csrf())
;
} |
We just worked our way through this same rabbit hole at my job and came up with a solution that works in the latest versions. For us, the key pieces were:
Set your private WebTestClient webTestClient;
@Autowired
public void setWebApplicationContext(final WebApplicationContext context) {
webTestClient = MockMvcWebTestClient
.bindToApplicationContext(context)
.apply(SecurityMockMvcConfigurers.springSecurity())
.build();
} this portion of the solution was found in #9304 (comment) Then, you can annotate your test classes / methods with Full working test class calling a controller that echoes the principal's name: @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class WebTestClientMockUserTest {
private WebTestClient webTestClient;
@Autowired
public void setWebApplicationContext(final WebApplicationContext context) {
webTestClient = MockMvcWebTestClient
.bindToApplicationContext(context)
.apply(SecurityMockMvcConfigurers.springSecurity())
.build();
}
@Test
@WithMockUser("Austin")
void testWithMockUser() {
webTestClient
.get()
.uri("/whoami")
.exchange()
.expectStatus()
.isOk()
.expectBody(String.class)
.isEqualTo("You are Austin");
}
} I think the thing that is causing everyone the most trouble is that you need to mock the security in a WebMVC-first way, not the WebFlux-first way exposed on |
Since version 6.1 there seems to be a possibility to use fun WebTestClient.mutateWith(rpp: RequestPostProcessor) =
this.mutateWith { builder, _, connector ->
(connector as? MockMvcHttpConnector)?.let { mockMvcConnector ->
builder.clientConnector(mockMvcConnector.with(listOf(rpp)))
}
} This is simple kotlin extension function to |
Describe the bug
mockJwt() WebTestClientConfigurer
does not seem to work withMockMvcWebTestClient
that was introduced in Spring 5.3 as documented.To Reproduce
throws the exception below
while the equivalent following tests work
Expected behavior
The first example works
Sample
https://github.com/making/spring-security-gh-9257
The text was updated successfully, but these errors were encountered: