/*
* 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.cors;
import java.util.Collections;
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.CollectionUtils;
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;
CorsConfigurationSource
that uses URL path patterns to select the CorsConfiguration
for a request. Pattern matching can be done with a PathMatcher
or with pre-parsed 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 resolved
String lookupPath or a parsed
RequestPath
with a fallback on PathMatcher
but the fallback can be disabled. For more details, please see setAllowInitLookupPath(boolean)
.
Author: Sebastien Deleuze, Rossen Stoyanchev See Also: Since: 4.2
/**
* {@code CorsConfigurationSource} that uses URL path patterns to select the
* {@code CorsConfiguration} for a request.
*
* <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 static PathMatcher defaultPathMatcher = new AntPathMatcher();
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<>();
Default constructor with PathPatternParser.defaultInstance
. /**
* Default constructor with {@link PathPatternParser#defaultInstance}.
*/
public UrlBasedCorsConfigurationSource() {
this(PathPatternParser.defaultInstance);
}
Constructor with a PathPatternParser
to parse patterns with. Params: - parser – the parser to use
Since: 5.3
/**
* 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
same property
on the configured UrlPathHelper
. Deprecated: as of 5.3 in favor of using setUrlPathHelper(UrlPathHelper)
, if at all. For further details, please see setAllowInitLookupPath(boolean)
.
/**
* 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 the same property
on the configured UrlPathHelper
. Deprecated: as of 5.3 in favor of using setUrlPathHelper(UrlPathHelper)
, if at all. For further details, please see setAllowInitLookupPath(boolean)
.
/**
* 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);
}
Shortcut to the
same property
on the configured UrlPathHelper
. Deprecated: as of 5.3 in favor of using setUrlPathHelper(UrlPathHelper)
, if at all. For further details, please see setAllowInitLookupPath(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();
}
}
Configure the UrlPathHelper
to resolve the lookupPath. This may not be necessary if the lookupPath is expected to be pre-resolved or if parsed PathPatterns
are used instead. For further details on that, see setAllowInitLookupPath(boolean)
. By default this is UrlPathHelper.defaultInstance
.
/**
* 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");
this.urlPathHelper = urlPathHelper;
}
When enabled, if there is neither a esolved
String lookupPath nor a parsed
RequestPath
then use the configured
UrlPathHelper
to resolve a String lookupPath. This in turn determines use of URL pattern matching with PathMatcher
or with parsed PathPattern
s. In Spring MVC, either a resolved String lookupPath or a parsed RequestPath
is always available within DispatcherServlet
processing. However in a Servlet Filter
such as CorsFilter
that may or may not be the case.
By default this is set to true
in which case lazy lookupPath initialization is allowed. Set this to false
when an application is using parsed PathPatterns
in which case the RequestPath
can be parsed earlier via
ServletRequestPathFilter
.
Params: - allowInitLookupPath – whether to disable lazy initialization
and fail if not already resolved
Since: 5.3
/**
* 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 UrlPathHelper.getLookupPathForRequest(HttpServletRequest)
. By default this is UrlPathHelper.PATH_ATTRIBUTE
.
Params: - name – the request attribute to check
Since: 5.2 Deprecated: as of 5.3 in favor of UrlPathHelper.PATH_ATTRIBUTE
.
/**
* 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 PathMatcher
to use for pattern matching. This is an advanced property that should be used only when a customized AntPathMatcher
or a custom PathMatcher is required.
By default this is AntPathMatcher
.
Note: Setting PathMatcher
enforces use of String pattern matching even when a parsed
RequestPath
is available.
/**
* 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.
For pattern syntax see AntPathMatcher
and PathPattern
as well as class-level Javadoc for details on which one may in use. Generally the syntax is largely the same with PathPattern
more tailored for web usage.
Params: - corsConfigurations – the mappings to use
See Also:
/**
* 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) {
corsConfigurations.forEach(this::registerCorsConfiguration);
}
}
Variant of setCorsConfigurations(Map<String,CorsConfiguration>)
to register one mapping at a time. Params: - pattern – the mapping pattern
- config – the CORS configuration to use for the pattern
See Also:
/**
* 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 void registerCorsConfiguration(String pattern, CorsConfiguration config) {
this.corsConfigurations.put(this.patternParser.parse(pattern), config);
}
Return all configured CORS mappings.
/**
* Return all configured CORS mappings.
*/
public Map<String, CorsConfiguration> getCorsConfigurations() {
Map<String, CorsConfiguration> result = CollectionUtils.newHashMap(this.corsConfigurations.size());
this.corsConfigurations.forEach((pattern, config) -> result.put(pattern.getPatternString(), config));
return Collections.unmodifiableMap(result);
}
@Override
@Nullable
public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
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));
}
}