Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -45,6 +45,7 @@
* @author Russell Allen
* @author Sebastien Deleuze
* @author Sam Brannen
* @author Ruslan Akhundov
* @since 4.2
*/
@Target({ElementType.TYPE, ElementType.METHOD})
Expand Down Expand Up @@ -93,6 +94,23 @@
@AliasFor("value")
String[] origins() default {};

/**
* The list of allowed origins patterns that be specific origins, e.g.
* {@code ".*\.domain1\.com"}, or {@code ".*"} for matching all origins.
* <p>A matched origin is listed in the {@code Access-Control-Allow-Origin}
* response header of preflight actual CORS requests.
* <p>By default all origins are allowed.
* <p><strong>Note:</strong> CORS checks use values from "Forwarded"
* (<a href="https://tools.ietf.org/html/rfc7239">RFC 7239</a>),
* "X-Forwarded-Host", "X-Forwarded-Port", and "X-Forwarded-Proto" headers,
* if present, in order to reflect the client-originated address.
* Consider using the {@code ForwardedHeaderFilter} in order to choose from a
* central place whether to extract and use, or to discard such headers.
* See the Spring Framework reference for more on this filter.
* @see #value
*/
String[] originsPatterns() default {};

/**
* The list of request headers that are permitted in actual requests,
* possibly {@code "*"} to allow all headers.
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -23,6 +23,7 @@
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import org.springframework.http.HttpMethod;
Expand All @@ -45,13 +46,16 @@
* @author Rossen Stoyanchev
* @author Juergen Hoeller
* @author Sam Brannen
* @author Ruslan Akhundov
* @since 4.2
* @see <a href="https://www.w3.org/TR/cors/">CORS spec</a>
*/
public class CorsConfiguration {

/** Wildcard representing <em>all</em> origins, methods, or headers. */
public static final String ALL = "*";
/** Wildcard representing pattern that matches <em>all</em> origins. */
public static final String ALL_PATTERN = ".*";

private static final List<HttpMethod> DEFAULT_METHODS = Collections.unmodifiableList(
Arrays.asList(HttpMethod.GET, HttpMethod.HEAD));
Expand All @@ -62,10 +66,19 @@ public class CorsConfiguration {
private static final List<String> DEFAULT_PERMIT_ALL = Collections.unmodifiableList(
Collections.singletonList(ALL));

private static final List<String> DEFAULT_PERMIT_ALL_PATTERN_STR = Collections.unmodifiableList(
Collections.singletonList(ALL_PATTERN));

private static final List<Pattern> DEFAULT_PERMIT_ALL_PATTERN = Collections.unmodifiableList(
Collections.singletonList(Pattern.compile(ALL_PATTERN)));


@Nullable
private List<String> allowedOrigins;

@Nullable
private List<Pattern> allowedOriginsPatterns;

@Nullable
private List<String> allowedMethods;

Expand Down Expand Up @@ -99,6 +112,7 @@ public CorsConfiguration() {
*/
public CorsConfiguration(CorsConfiguration other) {
this.allowedOrigins = other.allowedOrigins;
this.allowedOriginsPatterns = other.allowedOriginsPatterns;
this.allowedMethods = other.allowedMethods;
this.resolvedMethods = other.resolvedMethods;
this.allowedHeaders = other.allowedHeaders;
Expand Down Expand Up @@ -140,6 +154,54 @@ else if (this.allowedOrigins == DEFAULT_PERMIT_ALL) {
this.allowedOrigins.add(origin);
}

/**
* Set the origins patterns to allow, e.g. {@code "*.com"}.
* <p>By default this is not set.
*/
public CorsConfiguration setAllowedOriginsPatterns(@Nullable List<String> allowedOriginsPatterns) {
if (allowedOriginsPatterns == null) {
this.allowedOriginsPatterns = null;
}
else {
this.allowedOriginsPatterns = new ArrayList<>(allowedOriginsPatterns.size());
for (String pattern : allowedOriginsPatterns) {
this.allowedOriginsPatterns.add(Pattern.compile(pattern));
}
}

return this;
}

/**
* Return the configured origins patterns to allow, or {@code null} if none.
*
* @see #addAllowedOriginPattern(String)
* @see #setAllowedOriginsPatterns(List)
*/
@Nullable
public List<String> getAllowedOriginsPatterns() {
if (this.allowedOriginsPatterns == null) {
return null;
}
if (this.allowedOriginsPatterns == DEFAULT_PERMIT_ALL_PATTERN) {
return DEFAULT_PERMIT_ALL_PATTERN_STR;
}
return this.allowedOriginsPatterns.stream().map(Pattern::toString).collect(Collectors.toList());
}

/**
* Add an origin pattern to allow.
*/
public void addAllowedOriginPattern(String originPattern) {
if (this.allowedOriginsPatterns == null) {
this.allowedOriginsPatterns = new ArrayList<>(4);
}
else if (this.allowedOriginsPatterns == DEFAULT_PERMIT_ALL_PATTERN) {
setAllowedOriginsPatterns(DEFAULT_PERMIT_ALL_PATTERN_STR);
}
this.allowedOriginsPatterns.add(Pattern.compile(originPattern));
}

/**
* Set the HTTP methods to allow, e.g. {@code "GET"}, {@code "POST"},
* {@code "PUT"}, etc.
Expand Down Expand Up @@ -351,7 +413,7 @@ public Long getMaxAge() {
* </ul>
*/
public CorsConfiguration applyPermitDefaultValues() {
if (this.allowedOrigins == null) {
if (this.allowedOrigins == null && this.allowedOriginsPatterns == null) {
this.allowedOrigins = DEFAULT_PERMIT_ALL;
}
if (this.allowedMethods == null) {
Expand Down Expand Up @@ -392,7 +454,14 @@ public CorsConfiguration combine(@Nullable CorsConfiguration other) {
return this;
}
CorsConfiguration config = new CorsConfiguration(this);
config.setAllowedOrigins(combine(getAllowedOrigins(), other.getAllowedOrigins()));
List<String> combinedOrigins = combine(getAllowedOrigins(), other.getAllowedOrigins());
List<String> combinedOriginsPatterns = combine(getAllowedOriginsPatterns(), other.getAllowedOriginsPatterns());
if (combinedOrigins == DEFAULT_PERMIT_ALL && combinedOriginsPatterns != DEFAULT_PERMIT_ALL_PATTERN_STR
&& !CollectionUtils.isEmpty(combinedOriginsPatterns)) {
combinedOrigins = null;
}
config.setAllowedOrigins(combinedOrigins);
config.setAllowedOriginsPatterns(combinedOriginsPatterns);
config.setAllowedMethods(combine(getAllowedMethods(), other.getAllowedMethods()));
config.setAllowedHeaders(combine(getAllowedHeaders(), other.getAllowedHeaders()));
config.setExposedHeaders(combine(getExposedHeaders(), other.getExposedHeaders()));
Expand All @@ -414,15 +483,20 @@ private List<String> combine(@Nullable List<String> source, @Nullable List<Strin
if (source == null) {
return other;
}
if (source == DEFAULT_PERMIT_ALL || source == DEFAULT_PERMIT_METHODS) {
if (source == DEFAULT_PERMIT_ALL || source == DEFAULT_PERMIT_METHODS
|| source == DEFAULT_PERMIT_ALL_PATTERN_STR) {
return other;
}
if (other == DEFAULT_PERMIT_ALL || other == DEFAULT_PERMIT_METHODS) {
if (other == DEFAULT_PERMIT_ALL || other == DEFAULT_PERMIT_METHODS
|| other == DEFAULT_PERMIT_ALL_PATTERN_STR) {
return source;
}
if (source.contains(ALL) || other.contains(ALL)) {
return new ArrayList<>(Collections.singletonList(ALL));
}
if ( source.contains(ALL_PATTERN) || other.contains(ALL_PATTERN)) {
return new ArrayList<>(Collections.singletonList(ALL_PATTERN));
}
Set<String> combined = new LinkedHashSet<>(source);
combined.addAll(other);
return new ArrayList<>(combined);
Expand All @@ -439,21 +513,35 @@ public String checkOrigin(@Nullable String requestOrigin) {
if (!StringUtils.hasText(requestOrigin)) {
return null;
}
if (ObjectUtils.isEmpty(this.allowedOrigins)) {
return null;
}

if (this.allowedOrigins.contains(ALL)) {
if (this.allowCredentials != Boolean.TRUE) {
return ALL;
if (!ObjectUtils.isEmpty(this.allowedOrigins)) {
if (this.allowedOrigins.contains(ALL)) {
if (this.allowCredentials != Boolean.TRUE) {
return ALL;
}
else {
return requestOrigin;
}
}
else {
return requestOrigin;
for (String allowedOrigin : this.allowedOrigins) {
if (requestOrigin.equalsIgnoreCase(allowedOrigin)) {
return requestOrigin;
}
}
}
for (String allowedOrigin : this.allowedOrigins) {
if (requestOrigin.equalsIgnoreCase(allowedOrigin)) {
return requestOrigin;
if (!ObjectUtils.isEmpty(this.allowedOriginsPatterns)) {
for (Pattern allowedOriginsPattern : this.allowedOriginsPatterns) {
if (allowedOriginsPattern.pattern().equals(ALL_PATTERN)) {
if (this.allowCredentials != Boolean.TRUE) {
return ALL;
}
else {
return requestOrigin;
}
}
else if (allowedOriginsPattern.matcher(requestOrigin).matches()) {
return requestOrigin;
}
}
}

Expand Down
Loading