SPR-5690 - Request header filtering in @RequestMapping
This commit is contained in:
parent
b8b74db01a
commit
afa461892f
|
|
@ -230,4 +230,25 @@ public @interface RequestMapping {
|
||||||
*/
|
*/
|
||||||
String[] params() default {};
|
String[] params() default {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The headers of the mapped request, narrowing the primary mapping.
|
||||||
|
* <p>Same format for any environment: a sequence of "My-Header=myValue" style
|
||||||
|
* expressions, with a request only mapped if each such header is found
|
||||||
|
* to have the given value. "My-Header" style expressions are also supported,
|
||||||
|
* with such headers having to be present in the request (allowed to have
|
||||||
|
* any value). Finally, "!My-Header" style expressions indicate that the
|
||||||
|
* specified header is <i>not</i> supposed to be present in the request.
|
||||||
|
* <p>Also supports media type wildcards (*), for headers such as Accept
|
||||||
|
* and Content-Type. For instance,
|
||||||
|
* <pre>
|
||||||
|
* @RequestMapping(value = "/something", headers = "content-type=text/*")
|
||||||
|
* </pre>
|
||||||
|
* will match requests with a Content-Type of "text/html", "text/plain", etc.
|
||||||
|
* <p><b>Supported at the type level as well as at the method level!</b>
|
||||||
|
* When used at the type level, all method-level mappings inherit
|
||||||
|
* this header restriction (i.e. the type-level restriction
|
||||||
|
* gets checked before the handler method is even resolved).
|
||||||
|
* @see org.springframework.http.MediaType
|
||||||
|
*/
|
||||||
|
String[] headers() default {};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -396,12 +396,15 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
|
||||||
if (!hasTypeLevelMapping() || !Arrays.equals(mapping.params(), getTypeLevelMapping().params())) {
|
if (!hasTypeLevelMapping() || !Arrays.equals(mapping.params(), getTypeLevelMapping().params())) {
|
||||||
mappingInfo.params = mapping.params();
|
mappingInfo.params = mapping.params();
|
||||||
}
|
}
|
||||||
|
if (!hasTypeLevelMapping() || !Arrays.equals(mapping.headers(), getTypeLevelMapping().headers())) {
|
||||||
|
mappingInfo.headers = mapping.headers();
|
||||||
|
}
|
||||||
boolean match = false;
|
boolean match = false;
|
||||||
if (mappingInfo.paths.length > 0) {
|
if (mappingInfo.paths.length > 0) {
|
||||||
List<String> matchedPaths = new ArrayList<String>(mappingInfo.paths.length);
|
List<String> matchedPaths = new ArrayList<String>(mappingInfo.paths.length);
|
||||||
for (String methodLevelPattern : mappingInfo.paths) {
|
for (String methodLevelPattern : mappingInfo.paths) {
|
||||||
if (isPathMatch(methodLevelPattern, lookupPath)) {
|
if (isPathMatch(methodLevelPattern, lookupPath)) {
|
||||||
if (checkParameters(mappingInfo, request)) {
|
if (mappingInfo.matches(request)) {
|
||||||
match = true;
|
match = true;
|
||||||
matchedPaths.add(methodLevelPattern);
|
matchedPaths.add(methodLevelPattern);
|
||||||
}
|
}
|
||||||
|
|
@ -418,7 +421,7 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// No paths specified: parameter match sufficient.
|
// No paths specified: parameter match sufficient.
|
||||||
match = checkParameters(mappingInfo, request);
|
match = mappingInfo.matches(request);
|
||||||
if (match && mappingInfo.methods.length == 0 && mappingInfo.params.length == 0 &&
|
if (match && mappingInfo.methods.length == 0 && mappingInfo.params.length == 0 &&
|
||||||
resolvedMethodName != null && !resolvedMethodName.equals(handlerMethod.getName())) {
|
resolvedMethodName != null && !resolvedMethodName.equals(handlerMethod.getName())) {
|
||||||
match = false;
|
match = false;
|
||||||
|
|
@ -514,11 +517,6 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean checkParameters(RequestMappingInfo mapping, HttpServletRequest request) {
|
|
||||||
return ServletAnnotationMappingUtils.checkRequestMethod(mapping.methods, request) &&
|
|
||||||
ServletAnnotationMappingUtils.checkParameters(mapping.params, request);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private void extractHandlerMethodUriTemplates(String mappedPath,
|
private void extractHandlerMethodUriTemplates(String mappedPath,
|
||||||
String lookupPath,
|
String lookupPath,
|
||||||
|
|
@ -726,21 +724,29 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
|
||||||
|
|
||||||
String[] params = new String[0];
|
String[] params = new String[0];
|
||||||
|
|
||||||
|
String[] headers = new String[0];
|
||||||
|
|
||||||
String bestMatchedPath() {
|
String bestMatchedPath() {
|
||||||
return matchedPaths.length > 0 ? matchedPaths[0] : null;
|
return matchedPaths.length > 0 ? matchedPaths[0] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean matches(HttpServletRequest request) {
|
||||||
|
return ServletAnnotationMappingUtils.checkRequestMethod(this.methods, request) &&
|
||||||
|
ServletAnnotationMappingUtils.checkParameters(this.params, request) &&
|
||||||
|
ServletAnnotationMappingUtils.checkHeaders(this.headers, request);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object obj) {
|
public boolean equals(Object obj) {
|
||||||
RequestMappingInfo other = (RequestMappingInfo) obj;
|
RequestMappingInfo other = (RequestMappingInfo) obj;
|
||||||
return (Arrays.equals(this.paths, other.paths) && Arrays.equals(this.methods, other.methods) &&
|
return (Arrays.equals(this.paths, other.paths) && Arrays.equals(this.methods, other.methods) &&
|
||||||
Arrays.equals(this.params, other.params));
|
Arrays.equals(this.params, other.params) && Arrays.equals(this.headers, other.headers));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return (Arrays.hashCode(this.paths) * 29 + Arrays.hashCode(this.methods) * 31 +
|
return (Arrays.hashCode(this.paths) * 23 + Arrays.hashCode(this.methods) * 29 +
|
||||||
Arrays.hashCode(this.params));
|
Arrays.hashCode(this.params) * 31 + Arrays.hashCode(this.headers));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -777,14 +783,21 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
|
||||||
}
|
}
|
||||||
else if (info1MethodCount == 1 & info2MethodCount > 1) {
|
else if (info1MethodCount == 1 & info2MethodCount > 1) {
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
}
|
}
|
||||||
else if (info2MethodCount == 1 & info1MethodCount > 1) {
|
else if (info2MethodCount == 1 & info1MethodCount > 1) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
int info1ParamCount = info1.params.length;
|
int info1ParamCount = info1.params.length;
|
||||||
int info2ParamCount = info2.params.length;
|
int info2ParamCount = info2.params.length;
|
||||||
return (info1ParamCount < info2ParamCount ? 1 : (info1ParamCount == info2ParamCount ? 0 : -1));
|
if (info1ParamCount != info2ParamCount) {
|
||||||
|
return info2ParamCount - info1ParamCount;
|
||||||
|
}
|
||||||
|
int info1HeaderCount = info1.headers.length;
|
||||||
|
int info2HeaderCount = info2.headers.length;
|
||||||
|
if (info1HeaderCount != info2HeaderCount) {
|
||||||
|
return info2HeaderCount - info1HeaderCount;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,11 @@
|
||||||
|
|
||||||
package org.springframework.web.servlet.mvc.annotation;
|
package org.springframework.web.servlet.mvc.annotation;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.util.ObjectUtils;
|
import org.springframework.util.ObjectUtils;
|
||||||
import org.springframework.web.bind.annotation.RequestMethod;
|
import org.springframework.web.bind.annotation.RequestMethod;
|
||||||
import org.springframework.web.util.WebUtils;
|
import org.springframework.web.util.WebUtils;
|
||||||
|
|
@ -32,6 +35,7 @@ abstract class ServletAnnotationMappingUtils {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check whether the given request matches the specified request methods.
|
* Check whether the given request matches the specified request methods.
|
||||||
|
*
|
||||||
* @param methods the HTTP request methods to check against
|
* @param methods the HTTP request methods to check against
|
||||||
* @param request the current HTTP request to check
|
* @param request the current HTTP request to check
|
||||||
*/
|
*/
|
||||||
|
|
@ -49,8 +53,8 @@ abstract class ServletAnnotationMappingUtils {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check whether the given request matches the specified parameter conditions.
|
* Check whether the given request matches the specified parameter conditions.
|
||||||
* @param params the parameter conditions, following
|
*
|
||||||
* {@link org.springframework.web.bind.annotation.RequestMapping#params()}
|
* @param params the parameter conditions, following {@link org.springframework.web.bind.annotation.RequestMapping#params()}
|
||||||
* @param request the current HTTP request to check
|
* @param request the current HTTP request to check
|
||||||
*/
|
*/
|
||||||
public static boolean checkParameters(String[] params, HttpServletRequest request) {
|
public static boolean checkParameters(String[] params, HttpServletRequest request) {
|
||||||
|
|
@ -79,4 +83,62 @@ abstract class ServletAnnotationMappingUtils {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether the given request matches the specified header conditions.
|
||||||
|
*
|
||||||
|
* @param headers the header conditions, following {@link org.springframework.web.bind.annotation.RequestMapping#headers()}
|
||||||
|
* @param request the current HTTP request to check
|
||||||
|
*/
|
||||||
|
public static boolean checkHeaders(String[] headers, HttpServletRequest request) {
|
||||||
|
if (!ObjectUtils.isEmpty(headers)) {
|
||||||
|
for (String header : headers) {
|
||||||
|
int separator = header.indexOf('=');
|
||||||
|
if (separator == -1) {
|
||||||
|
if (header.startsWith("!")) {
|
||||||
|
if (hasHeader(request, header.substring(1))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!hasHeader(request, header)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
String key = header.substring(0, separator);
|
||||||
|
String value = header.substring(separator + 1);
|
||||||
|
if (isMediaTypeHeader(key)) {
|
||||||
|
List<MediaType> requestMediaTypes = MediaType.parseMediaTypes(request.getHeader(key));
|
||||||
|
List<MediaType> valueMediaTypes = MediaType.parseMediaTypes(value);
|
||||||
|
boolean found = false;
|
||||||
|
for (Iterator<MediaType> valIter = valueMediaTypes.iterator(); valIter.hasNext() && !found;) {
|
||||||
|
MediaType valueMediaType = valIter.next();
|
||||||
|
for (Iterator<MediaType> reqIter = requestMediaTypes.iterator(); reqIter.hasNext() && !found;) {
|
||||||
|
MediaType requestMediaType = reqIter.next();
|
||||||
|
if (valueMediaType.includes(requestMediaType)) {
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
if (!found) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!value.equals(request.getHeader(key))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean hasHeader(HttpServletRequest request, String headerName) {
|
||||||
|
return request.getHeader(headerName) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isMediaTypeHeader(String headerName) {
|
||||||
|
return "Accept".equalsIgnoreCase(headerName) || "Content-Type".equalsIgnoreCase(headerName);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -894,6 +894,23 @@ public class ServletAnnotationControllerTests {
|
||||||
assertEquals("Invalid response status code", HttpServletResponse.SC_BAD_REQUEST, response.getStatus());
|
assertEquals("Invalid response status code", HttpServletResponse.SC_BAD_REQUEST, response.getStatus());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void headers() throws ServletException, IOException {
|
||||||
|
initServlet(HeadersController.class);
|
||||||
|
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/something");
|
||||||
|
request.addHeader("Content-Type", "application/pdf");
|
||||||
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
servlet.service(request, response);
|
||||||
|
assertEquals("pdf", response.getContentAsString());
|
||||||
|
|
||||||
|
request = new MockHttpServletRequest("GET", "/something");
|
||||||
|
request.addHeader("Content-Type", "text/html");
|
||||||
|
response = new MockHttpServletResponse();
|
||||||
|
servlet.service(request, response);
|
||||||
|
assertEquals("text", response.getContentAsString());
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Controllers
|
* Controllers
|
||||||
*/
|
*/
|
||||||
|
|
@ -1472,6 +1489,20 @@ public class ServletAnnotationControllerTests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Controller
|
||||||
|
public static class HeadersController {
|
||||||
|
|
||||||
|
@RequestMapping(value = "/something", headers = "content-type=application/pdf")
|
||||||
|
public void handlePdf(Writer writer) throws IOException {
|
||||||
|
writer.write("pdf");
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequestMapping(value = "/something", headers = "content-type=text/*")
|
||||||
|
public void handleHtml(Writer writer) throws IOException {
|
||||||
|
writer.write("text");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public static class MyMessageConverter implements HttpMessageConverter {
|
public static class MyMessageConverter implements HttpMessageConverter {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,22 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2009 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package org.springframework.web.servlet.mvc.annotation;
|
package org.springframework.web.servlet.mvc.annotation;
|
||||||
|
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.*;
|
||||||
import static org.junit.Assert.assertTrue;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import org.springframework.mock.web.MockHttpServletRequest;
|
import org.springframework.mock.web.MockHttpServletRequest;
|
||||||
|
|
@ -26,4 +41,96 @@ public class ServletAnnotationMappingUtilsTests {
|
||||||
assertFalse("Invalid request method result", result);
|
assertFalse("Invalid request method result", result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void checkParametersSimpleMatch() {
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/");
|
||||||
|
request.addParameter("param1", "value1");
|
||||||
|
String[] params = new String[]{"param1", "!param2"};
|
||||||
|
boolean result = ServletAnnotationMappingUtils.checkParameters(params, request);
|
||||||
|
assertTrue("Invalid request method result", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void checkParametersSimpleNoMatch() {
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/");
|
||||||
|
request.addParameter("param1", "value1");
|
||||||
|
request.addParameter("param2", "value2");
|
||||||
|
String[] params = new String[]{"param1", "!param2"};
|
||||||
|
boolean result = ServletAnnotationMappingUtils.checkParameters(params, request);
|
||||||
|
assertFalse("Invalid request method result", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void checkParametersKeyValueMatch() {
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/");
|
||||||
|
request.addParameter("param1", "value1");
|
||||||
|
String[] params = new String[]{"param1=value1"};
|
||||||
|
boolean result = ServletAnnotationMappingUtils.checkParameters(params, request);
|
||||||
|
assertTrue("Invalid request method result", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void checkParametersKeyValueNoMatch() {
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/");
|
||||||
|
request.addParameter("param1", "value1");
|
||||||
|
String[] params = new String[]{"param1=foo"};
|
||||||
|
boolean result = ServletAnnotationMappingUtils.checkParameters(params, request);
|
||||||
|
assertFalse("Invalid request method result", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void checkHeadersSimpleMatch() {
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/");
|
||||||
|
request.addHeader("header1", "value1");
|
||||||
|
String[] headers = new String[]{"header1", "!header2"};
|
||||||
|
boolean result = ServletAnnotationMappingUtils.checkHeaders(headers, request);
|
||||||
|
assertTrue("Invalid request method result", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void checkHeadersSimpleNoMatch() {
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/");
|
||||||
|
request.addHeader("header1", "value1");
|
||||||
|
request.addHeader("header2", "value2");
|
||||||
|
String[] headers = new String[]{"header1", "!header2"};
|
||||||
|
boolean result = ServletAnnotationMappingUtils.checkHeaders(headers, request);
|
||||||
|
assertFalse("Invalid request method result", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void checkHeadersKeyValueMatch() {
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/");
|
||||||
|
request.addHeader("header1", "value1");
|
||||||
|
String[] headers = new String[]{"header1=value1"};
|
||||||
|
boolean result = ServletAnnotationMappingUtils.checkHeaders(headers, request);
|
||||||
|
assertTrue("Invalid request method result", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void checkHeadersKeyValueNoMatch() {
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/");
|
||||||
|
request.addHeader("header1", "value1");
|
||||||
|
String[] headers = new String[]{"header1=foo"};
|
||||||
|
boolean result = ServletAnnotationMappingUtils.checkHeaders(headers, request);
|
||||||
|
assertFalse("Invalid request method result", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void checkHeadersAcceptMatch() {
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/");
|
||||||
|
request.addHeader("Accept", "application/pdf, text/html");
|
||||||
|
String[] headers = new String[]{"accept=text/html, application/*"};
|
||||||
|
boolean result = ServletAnnotationMappingUtils.checkHeaders(headers, request);
|
||||||
|
assertTrue("Invalid request method result", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void checkHeadersAcceptNoMatch() {
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/");
|
||||||
|
request.addHeader("Accept", "application/pdf, text/html");
|
||||||
|
String[] headers = new String[]{"accept=audio/basic, application/xml"};
|
||||||
|
boolean result = ServletAnnotationMappingUtils.checkHeaders(headers, request);
|
||||||
|
assertFalse("Invalid request method result", result);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue