org.springframework.web.cors.UrlBasedCorsConfigurationSource Maven / Gradle / Ivy
/*
* Copyright 2002-2022 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 jakarta.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;
/**
* {@code CorsConfigurationSource} that uses URL path patterns to select the
* {@code CorsConfiguration} for a request.
*
* 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 final 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 corsConfigurations = new LinkedHashMap<>();
/**
* Default constructor with {@link PathPatternParser#defaultInstance}.
*/
public UrlBasedCorsConfigurationSource() {
this(PathPatternParser.defaultInstance);
}
/**
* 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 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
* {@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 {@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)}.
* 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
* {@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.
*
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.
*
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)}.
*
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.
*
This is an advanced property that should be used only when a
* customized {@link AntPathMatcher} or a custom PathMatcher is required.
*
By default this is {@link AntPathMatcher}.
*
Note: 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 {@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 corsConfigurations) {
this.corsConfigurations.clear();
if (corsConfigurations != null) {
corsConfigurations.forEach(this::registerCorsConfiguration);
}
}
/**
* 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.
*/
public Map getCorsConfigurations() {
Map 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 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));
}
}