Skip to content

Commit 4a74136

Browse files
committed
formLogin() does not work with REST Docs
This commit adds a new test which fails right now, and also suggest a change. Fixes spring-projectsgh-7572
1 parent 1ca47f5 commit 4a74136

File tree

2 files changed

+100
-5
lines changed

2 files changed

+100
-5
lines changed

test/src/main/java/org/springframework/security/test/web/servlet/request/SecurityMockMvcRequestBuilders.java

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,21 @@
1515
*/
1616
package org.springframework.security.test.web.servlet.request;
1717

18-
import javax.servlet.ServletContext;
19-
18+
import org.springframework.beans.Mergeable;
2019
import org.springframework.http.MediaType;
2120
import org.springframework.mock.web.MockHttpServletRequest;
2221
import org.springframework.security.web.csrf.CsrfToken;
2322
import org.springframework.test.web.servlet.MockMvc;
2423
import org.springframework.test.web.servlet.RequestBuilder;
24+
import org.springframework.test.web.servlet.request.ConfigurableSmartRequestBuilder;
2525
import org.springframework.test.web.servlet.request.RequestPostProcessor;
2626
import org.springframework.web.util.UriComponentsBuilder;
2727

28+
import javax.servlet.ServletContext;
29+
import java.util.ArrayList;
30+
import java.util.Collections;
31+
import java.util.List;
32+
2833
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
2934
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
3035

@@ -132,23 +137,25 @@ private LogoutRequestBuilder() {
132137
* @author Rob Winch
133138
* @since 4.0
134139
*/
135-
public static final class FormLoginRequestBuilder implements RequestBuilder {
140+
public static final class FormLoginRequestBuilder implements RequestBuilder,
141+
ConfigurableSmartRequestBuilder<FormLoginRequestBuilder>, Mergeable {
136142
private String usernameParam = "username";
137143
private String passwordParam = "password";
138144
private String username = "user";
139145
private String password = "password";
140146
private String loginProcessingUrl = "/login";
141147
private MediaType acceptMediaType = MediaType.APPLICATION_FORM_URLENCODED;
142148

143-
private RequestPostProcessor postProcessor = csrf();
149+
private List<RequestPostProcessor> postProcessors = new ArrayList<>(Collections.singletonList(csrf()));
144150

145151
@Override
146152
public MockHttpServletRequest buildRequest(ServletContext servletContext) {
147153
MockHttpServletRequest request = post(this.loginProcessingUrl)
148154
.accept(this.acceptMediaType).param(this.usernameParam, this.username)
149155
.param(this.passwordParam, this.password)
150156
.buildRequest(servletContext);
151-
return this.postProcessor.postProcessRequest(request);
157+
158+
return postProcessRequest(request);
152159
}
153160

154161
/**
@@ -260,6 +267,34 @@ public FormLoginRequestBuilder acceptMediaType(MediaType acceptMediaType) {
260267

261268
private FormLoginRequestBuilder() {
262269
}
270+
271+
@Override public boolean isMergeEnabled() {
272+
return false;
273+
}
274+
275+
@Override
276+
public Object merge( Object parent ) {
277+
// Step 1: Get parent's postprocessors
278+
if (parent instanceof ConfigurableSmartRequestBuilder) {
279+
// We cannot do that because on ConfigurableSmartRequestBuilder interface there is no getter method
280+
// for the postprocessors.
281+
}
282+
// Step 2: add parent's postprocessors to this instance.
283+
return this;
284+
}
285+
286+
@Override
287+
public FormLoginRequestBuilder with( RequestPostProcessor requestPostProcessor ) {
288+
this.postProcessors.add(requestPostProcessor);
289+
return this;
290+
}
291+
292+
@Override public MockHttpServletRequest postProcessRequest( MockHttpServletRequest request ) {
293+
for(RequestPostProcessor postProcessor: postProcessors) {
294+
request = postProcessor.postProcessRequest(request);
295+
}
296+
return request;
297+
}
263298
}
264299

265300
private SecurityMockMvcRequestBuilders() {

test/src/test/java/org/springframework/security/test/web/servlet/request/SecurityMockMvcRequestBuildersFormLoginTests.java

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,23 @@
1818
import org.junit.Before;
1919
import org.junit.Test;
2020

21+
import org.springframework.beans.Mergeable;
2122
import org.springframework.http.MediaType;
2223
import org.springframework.mock.web.MockHttpServletRequest;
2324
import org.springframework.mock.web.MockServletContext;
2425
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.CsrfRequestPostProcessor;
2526
import org.springframework.security.web.csrf.CsrfToken;
27+
import org.springframework.test.web.servlet.RequestBuilder;
28+
import org.springframework.test.web.servlet.SmartRequestBuilder;
29+
import org.springframework.test.web.servlet.request.ConfigurableSmartRequestBuilder;
30+
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
31+
import org.springframework.test.web.servlet.request.RequestPostProcessor;
32+
import org.springframework.test.web.servlet.setup.AbstractMockMvcBuilder;
33+
import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder;
34+
import org.springframework.util.Assert;
35+
import org.springframework.web.context.support.GenericWebApplicationContext;
36+
37+
import javax.servlet.ServletContext;
2638

2739
import static org.assertj.core.api.Assertions.assertThat;
2840
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin;
@@ -82,6 +94,46 @@ public void customWithUriVars() {
8294
assertThat(request.getRequestURI()).isEqualTo("/uri-login/val1/val2");
8395
}
8496

97+
@Test
98+
public void postProcessorsAreMergedDuringMockMvcPerform() throws Exception {
99+
100+
RequestBuilder requestBuilder = formLogin()
101+
.user("my-user")
102+
.password("my-password")
103+
.loginProcessingUrl("/my-path");
104+
105+
// spring-restdocs project creates request postprocessors using this hook:
106+
// org.springframework.test.web.servlet.setup.MockMvcConfigurer.beforeMockMvcCreated
107+
// The postprocessors are bound to the defaultRequestBuilder instance (with urlTemplate "/") here:
108+
// org.springframework.test.web.servlet.setup.AbstractMockMvcBuilder.build
109+
// The bind happens only if the default builder implements the ConfigurableSmartRequestBuilder interface.
110+
111+
RequestBuilder defaultRequestBuilder = MockMvcRequestBuilders.get("/");
112+
if (defaultRequestBuilder instanceof ConfigurableSmartRequestBuilder) {
113+
((ConfigurableSmartRequestBuilder) defaultRequestBuilder).with(new MockPostProcessor());
114+
}
115+
116+
// In the following method:
117+
// org.springframework.test.web.servlet.MockMvc.perform
118+
// the RequestBuilder is normally merged with the defaultRequestBuilder, so the spring-restdocs postprocessor
119+
// can be executed and it can generate nice documentations.
120+
// The problem is, that this merge happens only if the RequestBuilder implements Mergeable interface.
121+
122+
if (defaultRequestBuilder != null && requestBuilder instanceof Mergeable ) {
123+
requestBuilder = (RequestBuilder) ((Mergeable) requestBuilder).merge(defaultRequestBuilder);
124+
}
125+
126+
// Currently, none of SecurityMockMvcRequestBuilders implements this interface.
127+
// They should implement ConfigurableSmartRequestBuilder interface also to be able to take over
128+
// the postprocessors (and execute them).
129+
130+
MockHttpServletRequest request = requestBuilder.buildRequest(this.servletContext);
131+
132+
Assert.isInstanceOf(ConfigurableSmartRequestBuilder.class, requestBuilder);
133+
Assert.isTrue(request.equals(((SmartRequestBuilder) requestBuilder).postProcessRequest(request)),
134+
"Postprocessing failed.");
135+
}
136+
85137
// gh-3920
86138
@Test
87139
public void usesAcceptMediaForContentNegotiation() {
@@ -91,4 +143,12 @@ public void usesAcceptMediaForContentNegotiation() {
91143
assertThat(request.getHeader("Accept"))
92144
.isEqualTo(MediaType.APPLICATION_FORM_URLENCODED_VALUE);
93145
}
146+
147+
private class MockPostProcessor implements RequestPostProcessor {
148+
149+
@Override
150+
public MockHttpServletRequest postProcessRequest( MockHttpServletRequest request ) {
151+
return request;
152+
}
153+
}
94154
}

0 commit comments

Comments
 (0)