Support for parsed PathPatterns in Spring MVC
See gh-24945
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2020 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.
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package org.springframework.http.server;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
@@ -37,8 +36,8 @@ class DefaultRequestPath implements RequestPath {
|
||||
private final PathContainer pathWithinApplication;
|
||||
|
||||
|
||||
DefaultRequestPath(URI uri, @Nullable String contextPath) {
|
||||
this.fullPath = PathContainer.parsePath(uri.getRawPath());
|
||||
DefaultRequestPath(String rawPath, @Nullable String contextPath) {
|
||||
this.fullPath = PathContainer.parsePath(rawPath);
|
||||
this.contextPath = initContextPath(this.fullPath, contextPath);
|
||||
this.pathWithinApplication = extractPathWithinApplication(this.fullPath, this.contextPath);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2020 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.
|
||||
@@ -20,7 +20,11 @@ import java.net.URI;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Represents the complete path for a request.
|
||||
* Specialization of {@link PathContainer} that sub-divides the path into a
|
||||
* {@link #contextPath()} and the remaining {@link #pathWithinApplication()}.
|
||||
* The lattery is typically used for request mapping within the application
|
||||
* while the former is useful when preparing external links that point back to
|
||||
* the application.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 5.0
|
||||
@@ -40,7 +44,8 @@ public interface RequestPath extends PathContainer {
|
||||
PathContainer contextPath();
|
||||
|
||||
/**
|
||||
* The portion of the request path after the context path.
|
||||
* The portion of the request path after the context path which is typically
|
||||
* used for request mapping within the application .
|
||||
*/
|
||||
PathContainer pathWithinApplication();
|
||||
|
||||
@@ -54,10 +59,23 @@ public interface RequestPath extends PathContainer {
|
||||
|
||||
|
||||
/**
|
||||
* Create a new {@code RequestPath} with the given parameters.
|
||||
* Parse the URI for a request into a {@code RequestPath}.
|
||||
* @param uri the URI of the request
|
||||
* @param contextPath the contextPath portion of the URI path
|
||||
*/
|
||||
static RequestPath parse(URI uri, @Nullable String contextPath) {
|
||||
return new DefaultRequestPath(uri, contextPath);
|
||||
return parse(uri.getRawPath(), contextPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Variant of {@link #parse(URI, String)} with the encoded
|
||||
* {@link URI#getRawPath() raw path}.
|
||||
* @param rawPath the path
|
||||
* @param contextPath the contextPath portion of the URI path
|
||||
* @since 5.3
|
||||
*/
|
||||
static RequestPath parse(String rawPath, @Nullable String contextPath) {
|
||||
return new DefaultRequestPath(rawPath, contextPath);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2020 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.
|
||||
@@ -17,87 +17,131 @@
|
||||
package org.springframework.web.cors;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.http.server.PathContainer;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.PathMatcher;
|
||||
import org.springframework.web.util.ServletRequestPathUtils;
|
||||
import org.springframework.web.util.UrlPathHelper;
|
||||
import org.springframework.web.util.pattern.PathPattern;
|
||||
import org.springframework.web.util.pattern.PathPatternParser;
|
||||
|
||||
/**
|
||||
* Provide a per request {@link CorsConfiguration} instance based on a
|
||||
* collection of {@link CorsConfiguration} mapped on path patterns.
|
||||
* {@code CorsConfigurationSource} that uses URL path patterns to select the
|
||||
* {@code CorsConfiguration} for a request.
|
||||
*
|
||||
* <p>Exact path mapping URIs (such as {@code "/admin"}) are supported
|
||||
* as well as Ant-style path patterns (such as {@code "/admin/**"}).
|
||||
* <p>Pattern matching can be done with a {@link PathMatcher} or with pre-parsed
|
||||
* {@link PathPattern}s. The syntax is largely the same with the latter being more
|
||||
* tailored for web usage and more efficient. The choice depends on the presence of a
|
||||
* {@link UrlPathHelper#resolveAndCacheLookupPath resolved} String lookupPath or a
|
||||
* {@link ServletRequestPathUtils#parseAndCache parsed} {@code RequestPath}
|
||||
* with a fallback on {@link PathMatcher} but the fallback can be disabled.
|
||||
* For more details, please see {@link #setAllowInitLookupPath(boolean)}.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.2
|
||||
* @see PathPattern
|
||||
* @see AntPathMatcher
|
||||
*/
|
||||
public class UrlBasedCorsConfigurationSource implements CorsConfigurationSource {
|
||||
|
||||
private final Map<String, CorsConfiguration> corsConfigurations = new LinkedHashMap<>();
|
||||
private static PathMatcher defaultPathMatcher = new AntPathMatcher();
|
||||
|
||||
private PathMatcher pathMatcher = new AntPathMatcher();
|
||||
|
||||
private UrlPathHelper urlPathHelper = new UrlPathHelper();
|
||||
private final PathPatternParser patternParser;
|
||||
|
||||
private UrlPathHelper urlPathHelper = UrlPathHelper.defaultInstance;
|
||||
|
||||
private PathMatcher pathMatcher = defaultPathMatcher;
|
||||
|
||||
@Nullable
|
||||
private String lookupPathAttributeName;
|
||||
|
||||
private boolean allowInitLookupPath = true;
|
||||
|
||||
private final Map<PathPattern, CorsConfiguration> corsConfigurations = new LinkedHashMap<>();
|
||||
|
||||
|
||||
/**
|
||||
* Set the PathMatcher implementation to use for matching URL paths
|
||||
* against registered URL patterns. Default is AntPathMatcher.
|
||||
* @see org.springframework.util.AntPathMatcher
|
||||
* Default constructor with {@link PathPatternParser#defaultInstance}.
|
||||
*/
|
||||
public void setPathMatcher(PathMatcher pathMatcher) {
|
||||
Assert.notNull(pathMatcher, "PathMatcher must not be null");
|
||||
this.pathMatcher = pathMatcher;
|
||||
public UrlBasedCorsConfigurationSource() {
|
||||
this(PathPatternParser.defaultInstance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shortcut to same property on underlying {@link #setUrlPathHelper UrlPathHelper}.
|
||||
* @see org.springframework.web.util.UrlPathHelper#setAlwaysUseFullPath
|
||||
* Constructor with a {@link PathPatternParser} to parse patterns with.
|
||||
* @param parser the parser to use
|
||||
* @since 5.3
|
||||
*/
|
||||
public UrlBasedCorsConfigurationSource(PathPatternParser parser) {
|
||||
Assert.notNull(parser, "PathPatternParser must not be null");
|
||||
this.patternParser = parser;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Shortcut to the
|
||||
* {@link org.springframework.web.util.UrlPathHelper#setAlwaysUseFullPath
|
||||
* same property} on the configured {@code UrlPathHelper}.
|
||||
* @deprecated as of 5.3 in favor of using
|
||||
* {@link #setUrlPathHelper(UrlPathHelper)}, if at all. For further details,
|
||||
* please see {@link #setAllowInitLookupPath(boolean)}.
|
||||
*/
|
||||
@Deprecated
|
||||
public void setAlwaysUseFullPath(boolean alwaysUseFullPath) {
|
||||
initUrlPathHelper();
|
||||
this.urlPathHelper.setAlwaysUseFullPath(alwaysUseFullPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shortcut to same property on underlying {@link #setUrlPathHelper UrlPathHelper}.
|
||||
* @see org.springframework.web.util.UrlPathHelper#setUrlDecode
|
||||
* Shortcut to the
|
||||
* {@link org.springframework.web.util.UrlPathHelper#setUrlDecode same property}
|
||||
* on the configured {@code UrlPathHelper}.
|
||||
* @deprecated as of 5.3 in favor of using
|
||||
* {@link #setUrlPathHelper(UrlPathHelper)}, if at all. For further details,
|
||||
* please see {@link #setAllowInitLookupPath(boolean)}.
|
||||
*/
|
||||
@Deprecated
|
||||
public void setUrlDecode(boolean urlDecode) {
|
||||
initUrlPathHelper();
|
||||
this.urlPathHelper.setUrlDecode(urlDecode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Optionally configure the name of the attribute that caches the lookupPath.
|
||||
* This is used to make the call to
|
||||
* {@link UrlPathHelper#getLookupPathForRequest(HttpServletRequest, String)}
|
||||
* @param lookupPathAttributeName the request attribute to check
|
||||
* @since 5.2
|
||||
*/
|
||||
public void setLookupPathAttributeName(@Nullable String lookupPathAttributeName) {
|
||||
this.lookupPathAttributeName = lookupPathAttributeName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shortcut to same property on underlying {@link #setUrlPathHelper UrlPathHelper}.
|
||||
* @see org.springframework.web.util.UrlPathHelper#setRemoveSemicolonContent(boolean)
|
||||
* Shortcut to the
|
||||
* {@link org.springframework.web.util.UrlPathHelper#setRemoveSemicolonContent
|
||||
* same property} on the configured {@code UrlPathHelper}.
|
||||
* @deprecated as of 5.3 in favor of using
|
||||
* {@link #setUrlPathHelper(UrlPathHelper)}, if at all. For further details,
|
||||
* please see {@link #setAllowInitLookupPath(boolean)}.
|
||||
*/
|
||||
@Deprecated
|
||||
public void setRemoveSemicolonContent(boolean removeSemicolonContent) {
|
||||
initUrlPathHelper();
|
||||
this.urlPathHelper.setRemoveSemicolonContent(removeSemicolonContent);
|
||||
}
|
||||
|
||||
private void initUrlPathHelper() {
|
||||
if (this.urlPathHelper == UrlPathHelper.defaultInstance) {
|
||||
this.urlPathHelper = new UrlPathHelper();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the UrlPathHelper to use for resolution of lookup paths.
|
||||
* <p>Use this to override the default UrlPathHelper with a custom subclass.
|
||||
* Configure the {@code UrlPathHelper} to resolve the lookupPath. This may
|
||||
* not be necessary if the lookupPath is expected to be pre-resolved or if
|
||||
* parsed {@code PathPatterns} are used instead.
|
||||
* For further details on that, see {@link #setAllowInitLookupPath(boolean)}.
|
||||
* <p>By default this is {@link UrlPathHelper#defaultInstance}.
|
||||
*/
|
||||
public void setUrlPathHelper(UrlPathHelper urlPathHelper) {
|
||||
Assert.notNull(urlPathHelper, "UrlPathHelper must not be null");
|
||||
@@ -105,40 +149,127 @@ public class UrlBasedCorsConfigurationSource implements CorsConfigurationSource
|
||||
}
|
||||
|
||||
/**
|
||||
* Set CORS configuration based on URL patterns.
|
||||
* When enabled, if there is neither a
|
||||
* {@link UrlPathHelper#resolveAndCacheLookupPath esolved} String lookupPath nor a
|
||||
* {@link ServletRequestPathUtils#parseAndCache parsed} {@code RequestPath}
|
||||
* then use the {@link #setUrlPathHelper configured} {@code UrlPathHelper}
|
||||
* to resolve a String lookupPath. This in turn determines use of URL
|
||||
* pattern matching with {@link PathMatcher} or with parsed {@link PathPattern}s.
|
||||
* <p>In Spring MVC, either a resolved String lookupPath or a parsed
|
||||
* {@code RequestPath} is always available within {@code DispatcherServlet}
|
||||
* processing. However in a Servlet {@code Filter} such as {@code CorsFilter}
|
||||
* that may or may not be the case.
|
||||
* <p>By default this is set to {@code true} in which case lazy lookupPath
|
||||
* initialization is allowed. Set this to {@code false} when an
|
||||
* application is using parsed {@code PathPatterns} in which case the
|
||||
* {@code RequestPath} can be parsed earlier via
|
||||
* {@link org.springframework.web.filter.ServletRequestPathFilter
|
||||
* ServletRequestPathFilter}.
|
||||
* @param allowInitLookupPath whether to disable lazy initialization
|
||||
* and fail if not already resolved
|
||||
* @since 5.3
|
||||
*/
|
||||
public void setAllowInitLookupPath(boolean allowInitLookupPath) {
|
||||
this.allowInitLookupPath = allowInitLookupPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the name of the attribute that holds the lookupPath extracted
|
||||
* via {@link UrlPathHelper#getLookupPathForRequest(HttpServletRequest)}.
|
||||
* <p>By default this is {@link UrlPathHelper#PATH_ATTRIBUTE}.
|
||||
* @param name the request attribute to check
|
||||
* @since 5.2
|
||||
* @deprecated as of 5.3 in favor of {@link UrlPathHelper#PATH_ATTRIBUTE}.
|
||||
*/
|
||||
@Deprecated
|
||||
public void setLookupPathAttributeName(String name) {
|
||||
this.lookupPathAttributeName = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure a {@code PathMatcher} to use for pattern matching.
|
||||
* <p>This is an advanced property that should be used only when a
|
||||
* customized {@link AntPathMatcher} or a custom PathMatcher is required.
|
||||
* <p>By default this is {@link AntPathMatcher}.
|
||||
* <p><strong>Note:</strong> Setting {@code PathMatcher} enforces use of
|
||||
* String pattern matching even when a
|
||||
* {@link ServletRequestPathUtils#parseAndCache parsed} {@code RequestPath}
|
||||
* is available.
|
||||
*/
|
||||
public void setPathMatcher(PathMatcher pathMatcher) {
|
||||
this.pathMatcher = pathMatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the CORS configuration mappings.
|
||||
* <p>For pattern syntax see {@link AntPathMatcher} and {@link PathPattern}
|
||||
* as well as class-level Javadoc for details on which one may in use.
|
||||
* Generally the syntax is largely the same with {@link PathPattern} more
|
||||
* tailored for web usage.
|
||||
* @param corsConfigurations the mappings to use
|
||||
* @see PathPattern
|
||||
* @see AntPathMatcher
|
||||
*/
|
||||
public void setCorsConfigurations(@Nullable Map<String, CorsConfiguration> corsConfigurations) {
|
||||
this.corsConfigurations.clear();
|
||||
if (corsConfigurations != null) {
|
||||
this.corsConfigurations.putAll(corsConfigurations);
|
||||
corsConfigurations.forEach(this::registerCorsConfiguration);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the CORS configuration.
|
||||
* Variant of {@link #setCorsConfigurations(Map)} to register one mapping at a time.
|
||||
* @param pattern the mapping pattern
|
||||
* @param config the CORS configuration to use for the pattern
|
||||
* @see PathPattern
|
||||
* @see AntPathMatcher
|
||||
*/
|
||||
public Map<String, CorsConfiguration> getCorsConfigurations() {
|
||||
return Collections.unmodifiableMap(this.corsConfigurations);
|
||||
public void registerCorsConfiguration(String pattern, CorsConfiguration config) {
|
||||
this.corsConfigurations.put(this.patternParser.parse(pattern), config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a {@link CorsConfiguration} for the specified path pattern.
|
||||
* Return all configured CORS mappings.
|
||||
*/
|
||||
public void registerCorsConfiguration(String path, CorsConfiguration config) {
|
||||
this.corsConfigurations.put(path, config);
|
||||
public Map<String, CorsConfiguration> getCorsConfigurations() {
|
||||
Map<String, CorsConfiguration> result = new HashMap<>(this.corsConfigurations.size());
|
||||
this.corsConfigurations.forEach((pattern, config) -> result.put(pattern.getPatternString(), config));
|
||||
return Collections.unmodifiableMap(result);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
|
||||
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request, this.lookupPathAttributeName);
|
||||
for (Map.Entry<String, CorsConfiguration> entry : this.corsConfigurations.entrySet()) {
|
||||
if (this.pathMatcher.match(entry.getKey(), lookupPath)) {
|
||||
Object path = resolvePath(request);
|
||||
boolean isPathContainer = (path instanceof PathContainer);
|
||||
for (Map.Entry<PathPattern, CorsConfiguration> entry : this.corsConfigurations.entrySet()) {
|
||||
if (match(path, isPathContainer, entry.getKey())) {
|
||||
return entry.getValue();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private Object resolvePath(HttpServletRequest request) {
|
||||
if (this.allowInitLookupPath && !ServletRequestPathUtils.hasCachedPath(request)) {
|
||||
return (this.lookupPathAttributeName != null ?
|
||||
this.urlPathHelper.getLookupPathForRequest(request, this.lookupPathAttributeName) :
|
||||
this.urlPathHelper.getLookupPathForRequest(request));
|
||||
}
|
||||
Object lookupPath = ServletRequestPathUtils.getCachedPath(request);
|
||||
if (this.pathMatcher != defaultPathMatcher) {
|
||||
lookupPath = lookupPath.toString();
|
||||
}
|
||||
return lookupPath;
|
||||
}
|
||||
|
||||
private boolean match(Object path, boolean isPathContainer, PathPattern pattern) {
|
||||
return (isPathContainer ?
|
||||
pattern.matches((PathContainer) path) :
|
||||
this.pathMatcher.match(pattern.getPatternString(), (String) path));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -27,22 +27,20 @@ import org.springframework.web.util.pattern.PathPattern;
|
||||
import org.springframework.web.util.pattern.PathPatternParser;
|
||||
|
||||
/**
|
||||
* Provide a per reactive request {@link CorsConfiguration} instance based on a
|
||||
* collection of {@link CorsConfiguration} mapped on path patterns.
|
||||
*
|
||||
* <p>Exact path mapping URIs (such as {@code "/admin"}) are supported
|
||||
* as well as Ant-style path patterns (such as {@code "/admin/**"}).
|
||||
* {@code CorsConfigurationSource} that uses URL patterns to select the
|
||||
* {@code CorsConfiguration} for a request.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
* @author Brian Clozel
|
||||
* @since 5.0
|
||||
* @see PathPattern
|
||||
*/
|
||||
public class UrlBasedCorsConfigurationSource implements CorsConfigurationSource {
|
||||
|
||||
private final Map<PathPattern, CorsConfiguration> corsConfigurations;
|
||||
|
||||
private final PathPatternParser patternParser;
|
||||
|
||||
private final Map<PathPattern, CorsConfiguration> corsConfigurations = new LinkedHashMap<>();
|
||||
|
||||
|
||||
/**
|
||||
* Construct a new {@code UrlBasedCorsConfigurationSource} instance with default
|
||||
@@ -58,7 +56,6 @@ public class UrlBasedCorsConfigurationSource implements CorsConfigurationSource
|
||||
* {@code PathPatternParser}.
|
||||
*/
|
||||
public UrlBasedCorsConfigurationSource(PathPatternParser patternParser) {
|
||||
this.corsConfigurations = new LinkedHashMap<>();
|
||||
this.patternParser = patternParser;
|
||||
}
|
||||
|
||||
@@ -66,10 +63,10 @@ public class UrlBasedCorsConfigurationSource implements CorsConfigurationSource
|
||||
/**
|
||||
* Set CORS configuration based on URL patterns.
|
||||
*/
|
||||
public void setCorsConfigurations(@Nullable Map<String, CorsConfiguration> corsConfigurations) {
|
||||
public void setCorsConfigurations(@Nullable Map<String, CorsConfiguration> configMap) {
|
||||
this.corsConfigurations.clear();
|
||||
if (corsConfigurations != null) {
|
||||
corsConfigurations.forEach(this::registerCorsConfiguration);
|
||||
if (configMap != null) {
|
||||
configMap.forEach(this::registerCorsConfiguration);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,12 +80,13 @@ public class UrlBasedCorsConfigurationSource implements CorsConfigurationSource
|
||||
@Override
|
||||
@Nullable
|
||||
public CorsConfiguration getCorsConfiguration(ServerWebExchange exchange) {
|
||||
PathContainer lookupPath = exchange.getRequest().getPath().pathWithinApplication();
|
||||
return this.corsConfigurations.entrySet().stream()
|
||||
.filter(entry -> entry.getKey().matches(lookupPath))
|
||||
.map(Map.Entry::getValue)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
PathContainer path = exchange.getRequest().getPath().pathWithinApplication();
|
||||
for (Map.Entry<PathPattern, CorsConfiguration> entry : this.corsConfigurations.entrySet()) {
|
||||
if (entry.getKey().matches(path)) {
|
||||
return entry.getValue();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2020 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.
|
||||
@@ -32,24 +32,23 @@ import org.springframework.web.cors.DefaultCorsProcessor;
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||
|
||||
/**
|
||||
* {@link javax.servlet.Filter} that handles CORS preflight requests and intercepts
|
||||
* CORS simple and actual requests thanks to a {@link CorsProcessor} implementation
|
||||
* ({@link DefaultCorsProcessor} by default) in order to add the relevant CORS
|
||||
* response headers (like {@code Access-Control-Allow-Origin}) using the provided
|
||||
* {@link CorsConfigurationSource} (for example an {@link UrlBasedCorsConfigurationSource}
|
||||
* instance.
|
||||
* {@link javax.servlet.Filter} to handle CORS pre-flight requests and intercept
|
||||
* CORS simple and actual requests with a {@link CorsProcessor}, and to update
|
||||
* the response, e.g. with CORS response headers, based on the policy matched
|
||||
* through the provided {@link CorsConfigurationSource}.
|
||||
*
|
||||
* <p>This is an alternative to Spring MVC Java config and XML namespace CORS configuration,
|
||||
* useful for applications depending only on spring-web (not on spring-webmvc) or for
|
||||
* security constraints requiring CORS checks to be performed at {@link javax.servlet.Filter}
|
||||
* level.
|
||||
* <p>This is an alternative to configuring CORS in the Spring MVC Java config
|
||||
* and the Spring MVC XML namespace. It is useful for applications depending
|
||||
* only on spring-web (not on spring-webmvc) or for security constraints that
|
||||
* require CORS checks to be performed at {@link javax.servlet.Filter} level.
|
||||
*
|
||||
* <p>This filter could be used in conjunction with {@link DelegatingFilterProxy} in order
|
||||
* to help with its initialization.
|
||||
* <p>This filter could be used in conjunction with {@link DelegatingFilterProxy}
|
||||
* in order to help with its initialization.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
* @since 4.2
|
||||
* @see <a href="https://www.w3.org/TR/cors/">CORS W3C recommendation</a>
|
||||
* @see UrlBasedCorsConfigurationSource
|
||||
*/
|
||||
public class CorsFilter extends OncePerRequestFilter {
|
||||
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2002-2020 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
|
||||
*
|
||||
* https://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.filter;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.Filter;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.web.util.ServletRequestPathUtils;
|
||||
|
||||
/**
|
||||
* A {@code Filter} to {@link ServletRequestPathUtils#parseAndCache parse}
|
||||
* and cache a {@link org.springframework.http.server.RequestPath} for further
|
||||
* {@link ServletRequestPathUtils#getParsedRequestPath access} throughout the
|
||||
* filter chain. This is useful when parsed
|
||||
* {@link org.springframework.web.util.pattern.PathPattern}s are in use anywhere
|
||||
* in an application instead of String pattern matching with
|
||||
* {@link org.springframework.util.PathMatcher}.
|
||||
* <p>Note that in Spring MVC, the {@code DispatcherServlet} will also parse and
|
||||
* cache the {@code RequestPath} if it detects that parsed {@code PathPatterns}
|
||||
* are enabled for any {@code HandlerMapping} but it will skip doing that if it
|
||||
* finds the {@link ServletRequestPathUtils#PATH_ATTRIBUTE} already exists.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 5.3
|
||||
*/
|
||||
public class ServletRequestPathFilter implements Filter {
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
|
||||
throws IOException, ServletException {
|
||||
|
||||
ServletRequestPathUtils.parseAndCache((HttpServletRequest) request);
|
||||
try {
|
||||
chain.doFilter(request, response);
|
||||
}
|
||||
finally {
|
||||
ServletRequestPathUtils.clearParsedRequestPath(request);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* Copyright 2002-2020 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
|
||||
*
|
||||
* https://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.util;
|
||||
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.http.server.PathContainer;
|
||||
import org.springframework.http.server.RequestPath;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Utility class to parse the path of an {@link HttpServletRequest} to a
|
||||
* {@link RequestPath} and cache it in a request attribute for further access.
|
||||
* This can then be used for URL path matching with
|
||||
* {@link org.springframework.web.util.pattern.PathPattern PathPattern}s.
|
||||
*
|
||||
* <p>Also includes helper methods to return either a previously
|
||||
* {@link UrlPathHelper#resolveAndCacheLookupPath resolved} String lookupPath
|
||||
* or a previously {@link #parseAndCache parsed} {@code RequestPath} depending
|
||||
* on which is cached in request attributes.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 5.3
|
||||
*/
|
||||
public abstract class ServletRequestPathUtils {
|
||||
|
||||
/** Name of Servlet request attribute that holds the parsed {@link RequestPath}. */
|
||||
public static final String PATH_ATTRIBUTE = ServletRequestPathUtils.class.getName() + ".path";
|
||||
|
||||
|
||||
/**
|
||||
* Parse the {@link HttpServletRequest#getRequestURI() requestURI} of the
|
||||
* request and its {@code contextPath} to create a {@link RequestPath} and
|
||||
* cache it in the request attribute {@link #PATH_ATTRIBUTE}.
|
||||
*
|
||||
* <p>This method ignores the {@link HttpServletRequest#getServletPath()
|
||||
* servletPath} and the {@link HttpServletRequest#getPathInfo() pathInfo}.
|
||||
* Therefore in case of a Servlet mapping by prefix, the
|
||||
* {@link RequestPath#pathWithinApplication()} will always include the
|
||||
* Servlet prefix.
|
||||
*/
|
||||
public static RequestPath parseAndCache(HttpServletRequest request) {
|
||||
String requestUri = (String) request.getAttribute(WebUtils.INCLUDE_REQUEST_URI_ATTRIBUTE);
|
||||
requestUri = (requestUri != null ? requestUri : request.getRequestURI());
|
||||
RequestPath requestPath = RequestPath.parse(requestUri, request.getContextPath());
|
||||
request.setAttribute(PATH_ATTRIBUTE, requestPath);
|
||||
return requestPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link #parseAndCache previously} parsed and cached {@code RequestPath}.
|
||||
* @throws IllegalArgumentException if not found
|
||||
*/
|
||||
public static RequestPath getParsedRequestPath(ServletRequest request) {
|
||||
RequestPath path = (RequestPath) request.getAttribute(PATH_ATTRIBUTE);
|
||||
Assert.notNull(path, "Expected parsed RequestPath in request attribute \"" + PATH_ATTRIBUTE + "\".");
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for a {@link #parseAndCache previously} parsed and cached {@code RequestPath}.
|
||||
*/
|
||||
public static boolean hasParsedRequestPath(ServletRequest request) {
|
||||
return (request.getAttribute(PATH_ATTRIBUTE) != null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the request attribute {@link #PATH_ATTRIBUTE} that holds a
|
||||
* {@link #parseAndCache previously} parsed and cached {@code RequestPath}.
|
||||
*/
|
||||
public static void clearParsedRequestPath(ServletRequest request) {
|
||||
request.removeAttribute(PATH_ATTRIBUTE);
|
||||
}
|
||||
|
||||
|
||||
// Methods to select either parsed RequestPath or resolved String lookupPath
|
||||
|
||||
/**
|
||||
* Return the {@link UrlPathHelper#resolveAndCacheLookupPath pre-resolved}
|
||||
* String lookupPath or the {@link #parseAndCache(HttpServletRequest)
|
||||
* pre-parsed} {@code RequestPath}.
|
||||
* <p>In Spring MVC, when at least one {@code HandlerMapping} has parsed
|
||||
* {@code PathPatterns} enabled, the {@code DispatcherServlet} eagerly parses
|
||||
* and caches the {@code RequestPath} and the same can be also done earlier with
|
||||
* {@link org.springframework.web.filter.ServletRequestPathFilter
|
||||
* ServletRequestPathFilter}. In other cases where {@code HandlerMapping}s
|
||||
* use String pattern matching with {@code PathMatcher}, the String
|
||||
* lookupPath is resolved separately by each {@code HandlerMapping}.
|
||||
* @param request the current request
|
||||
* @return a String lookupPath or a {@code RequestPath}
|
||||
* @throws IllegalArgumentException if neither is available
|
||||
*/
|
||||
public static Object getCachedPath(ServletRequest request) {
|
||||
|
||||
// The RequestPath is pre-parsed if any HandlerMapping uses PathPatterns.
|
||||
// The lookupPath is re-resolved or cleared per HandlerMapping.
|
||||
// So check for lookupPath first.
|
||||
|
||||
String lookupPath = (String) request.getAttribute(UrlPathHelper.PATH_ATTRIBUTE);
|
||||
if (lookupPath != null) {
|
||||
return lookupPath;
|
||||
}
|
||||
RequestPath requestPath = (RequestPath) request.getAttribute(PATH_ATTRIBUTE);
|
||||
if (requestPath != null) {
|
||||
return requestPath.pathWithinApplication();
|
||||
}
|
||||
throw new IllegalArgumentException(
|
||||
"Neither a pre-parsed RequestPath nor a pre-resolved String lookupPath is available.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Variant of {@link #getCachedPath(ServletRequest)} that returns the path
|
||||
* for request mapping as a String.
|
||||
* <p>If the cached path is a {@link #parseAndCache(HttpServletRequest)
|
||||
* pre-parsed} {@code RequestPath} then the returned String path value is
|
||||
* encoded and with path parameters removed.
|
||||
* <p>If the cached path is a {@link UrlPathHelper#resolveAndCacheLookupPath
|
||||
* pre-resolved} String lookupPath, then the returned String path value
|
||||
* depends on how {@link UrlPathHelper} that resolved is configured.
|
||||
* @param request the current request
|
||||
* @return the full request mapping path as a String
|
||||
*/
|
||||
public static String getCachedPathValue(ServletRequest request) {
|
||||
Object path = getCachedPath(request);
|
||||
if (path instanceof PathContainer) {
|
||||
String value = ((PathContainer) path).value();
|
||||
path = UrlPathHelper.defaultInstance.removeSemicolonContent(value);
|
||||
}
|
||||
return (String) path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for a previously {@link UrlPathHelper#resolveAndCacheLookupPath
|
||||
* resolved} String lookupPath or a previously {@link #parseAndCache parsed}
|
||||
* {@code RequestPath}.
|
||||
* @param request the current request
|
||||
* @return whether a pre-resolved or pre-parsed path is available
|
||||
*/
|
||||
public static boolean hasCachedPath(ServletRequest request) {
|
||||
return (request.getAttribute(PATH_ATTRIBUTE) != null ||
|
||||
request.getAttribute(UrlPathHelper.PATH_ATTRIBUTE) != null);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.MappingMatch;
|
||||
|
||||
@@ -29,6 +30,7 @@ import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
@@ -51,6 +53,13 @@ import org.springframework.util.StringUtils;
|
||||
*/
|
||||
public class UrlPathHelper {
|
||||
|
||||
/**
|
||||
* Name of Servlet request attribute that holds a
|
||||
* {@link #getLookupPathForRequest resolved} lookupPath.
|
||||
* @since 5.3
|
||||
*/
|
||||
public static final String PATH_ATTRIBUTE = UrlPathHelper.class.getName() + ".path";
|
||||
|
||||
private static boolean isServlet4Present =
|
||||
ClassUtils.isPresent("javax.servlet.http.HttpServletMapping",
|
||||
UrlPathHelper.class.getClassLoader());
|
||||
@@ -160,24 +169,52 @@ public class UrlPathHelper {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@link #getLookupPathForRequest Resolve} the lookupPath and cache it in a
|
||||
* a request attribute with the key {@link #PATH_ATTRIBUTE} for subsequent
|
||||
* access via {@link #getResolvedLookupPath(ServletRequest)}.
|
||||
* @param request the current request
|
||||
* @return the resolved path
|
||||
* @since 5.3
|
||||
*/
|
||||
public String resolveAndCacheLookupPath(HttpServletRequest request) {
|
||||
String lookupPath = getLookupPathForRequest(request);
|
||||
request.setAttribute(PATH_ATTRIBUTE, lookupPath);
|
||||
return lookupPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a previously {@link #getLookupPathForRequest resolved} lookupPath.
|
||||
* @param request the current request
|
||||
* @return the previously resolved lookupPath
|
||||
* @throws IllegalArgumentException if the not found
|
||||
* @since 5.3
|
||||
*/
|
||||
public static String getResolvedLookupPath(ServletRequest request) {
|
||||
String lookupPath = (String) request.getAttribute(PATH_ATTRIBUTE);
|
||||
Assert.notNull(lookupPath, "Expected lookupPath in request attribute \"" + PATH_ATTRIBUTE + "\".");
|
||||
return lookupPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Variant of {@link #getLookupPathForRequest(HttpServletRequest)} that
|
||||
* automates checking for a previously computed lookupPath saved as a
|
||||
* request attribute. The attribute is only used for lookup purposes.
|
||||
* @param request current HTTP request
|
||||
* @param lookupPathAttributeName the request attribute to check
|
||||
* @param name the request attribute that holds the lookupPath
|
||||
* @return the lookup path
|
||||
* @since 5.2
|
||||
* @see org.springframework.web.servlet.HandlerMapping#LOOKUP_PATH
|
||||
* @deprecated as of 5.3 in favor of using
|
||||
* {@link #resolveAndCacheLookupPath(HttpServletRequest)} and
|
||||
* {@link #getResolvedLookupPath(ServletRequest)}.
|
||||
*/
|
||||
public String getLookupPathForRequest(HttpServletRequest request, @Nullable String lookupPathAttributeName) {
|
||||
if (lookupPathAttributeName != null) {
|
||||
String result = (String) request.getAttribute(lookupPathAttributeName);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
@Deprecated
|
||||
public String getLookupPathForRequest(HttpServletRequest request, @Nullable String name) {
|
||||
String result = null;
|
||||
if (name != null) {
|
||||
result = (String) request.getAttribute(name);
|
||||
}
|
||||
return getLookupPathForRequest(request);
|
||||
return (result != null ? result : getLookupPathForRequest(request));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user