Skip to content

Commit 33c48e7

Browse files
committed
Polish reactive CORS support
1 parent e31a2f7 commit 33c48e7

File tree

20 files changed

+481
-457
lines changed

20 files changed

+481
-457
lines changed

spring-web-reactive/src/main/java/org/springframework/web/reactive/config/CorsRegistration.java

+6-10
Original file line numberDiff line numberDiff line change
@@ -24,20 +24,16 @@
2424
import org.springframework.web.cors.CorsConfiguration;
2525

2626
/**
27-
* {@code CorsRegistration} assists with the creation of a
28-
* {@link CorsConfiguration} instance mapped to a path pattern.
27+
* Assists with the creation of a {@link CorsConfiguration} instance mapped to
28+
* a path pattern.
2929
*
30-
* <p>If no path pattern is specified, cross-origin request handling is
31-
* mapped to {@code "/**"}.
32-
*
33-
* <p>By default, all origins, all headers, credentials and {@code GET},
34-
* {@code HEAD}, and {@code POST} methods are allowed, and the max age is
35-
* set to 30 minutes.
30+
* <p>If no path pattern is specified, by default cross-origin request handling
31+
* is mapped to {@code "/**"}. Also by default, all origins, headers,
32+
* credentials and {@code GET}, {@code HEAD}, and {@code POST} methods are
33+
* allowed, while the max age is set to 30 minutes.
3634
*
3735
* @author Sebastien Deleuze
38-
* @author Sam Brannen
3936
* @since 5.0
40-
* @see CorsConfiguration
4137
* @see CorsRegistry
4238
*/
4339
public class CorsRegistration {

spring-web-reactive/src/main/java/org/springframework/web/reactive/config/CorsRegistry.java

+1
Original file line numberDiff line numberDiff line change
@@ -58,4 +58,5 @@ protected Map<String, CorsConfiguration> getCorsConfigurations() {
5858
}
5959
return configs;
6060
}
61+
6162
}

spring-web-reactive/src/main/java/org/springframework/web/reactive/config/WebReactiveConfiguration.java

+25-20
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@
4545
import org.springframework.http.codec.EncoderHttpMessageWriter;
4646
import org.springframework.http.codec.HttpMessageReader;
4747
import org.springframework.http.codec.HttpMessageWriter;
48+
import org.springframework.http.codec.Jackson2ServerHttpMessageReader;
49+
import org.springframework.http.codec.Jackson2ServerHttpMessageWriter;
4850
import org.springframework.http.codec.ResourceHttpMessageWriter;
4951
import org.springframework.http.codec.ServerSentEventHttpMessageWriter;
5052
import org.springframework.http.codec.json.Jackson2JsonDecoder;
@@ -61,8 +63,6 @@
6163
import org.springframework.web.reactive.handler.AbstractHandlerMapping;
6264
import org.springframework.web.reactive.result.SimpleHandlerAdapter;
6365
import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver;
64-
import org.springframework.http.codec.Jackson2ServerHttpMessageReader;
65-
import org.springframework.http.codec.Jackson2ServerHttpMessageWriter;
6666
import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerAdapter;
6767
import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerMapping;
6868
import org.springframework.web.reactive.result.method.annotation.ResponseBodyResultHandler;
@@ -90,14 +90,14 @@ public class WebReactiveConfiguration implements ApplicationContextAware {
9090
ClassUtils.isPresent("javax.xml.bind.Binder", WebReactiveConfiguration.class.getClassLoader());
9191

9292

93+
private Map<String, CorsConfiguration> corsConfigurations;
94+
9395
private PathMatchConfigurer pathMatchConfigurer;
9496

9597
private List<HttpMessageReader<?>> messageReaders;
9698

9799
private List<HttpMessageWriter<?>> messageWriters;
98100

99-
private Map<String, CorsConfiguration> corsConfigurations;
100-
101101
private ApplicationContext applicationContext;
102102

103103

@@ -171,6 +171,26 @@ protected Map<String, MediaType> getDefaultMediaTypeMappings() {
171171
protected void configureRequestedContentTypeResolver(RequestedContentTypeResolverBuilder builder) {
172172
}
173173

174+
/**
175+
* Callback for building the global CORS configuration. This method is final.
176+
* Use {@link #addCorsMappings(CorsRegistry)} to customize the CORS conifg.
177+
*/
178+
protected final Map<String, CorsConfiguration> getCorsConfigurations() {
179+
if (this.corsConfigurations == null) {
180+
CorsRegistry registry = new CorsRegistry();
181+
addCorsMappings(registry);
182+
this.corsConfigurations = registry.getCorsConfigurations();
183+
}
184+
return this.corsConfigurations;
185+
}
186+
187+
/**
188+
* Override this method to configure cross origin requests processing.
189+
* @see CorsRegistry
190+
*/
191+
protected void addCorsMappings(CorsRegistry registry) {
192+
}
193+
174194
/**
175195
* Callback for building the {@link PathMatchConfigurer}. This method is
176196
* final, use {@link #configurePathMatching} to customize path matching.
@@ -209,6 +229,7 @@ public HandlerMapping resourceHandlerMapping() {
209229
if (pathMatchConfigurer.getPathHelper() != null) {
210230
handlerMapping.setPathHelper(pathMatchConfigurer.getPathHelper());
211231
}
232+
212233
}
213234
else {
214235
handlerMapping = new EmptyHandlerMapping();
@@ -444,22 +465,6 @@ public ViewResolutionResultHandler viewResolutionResultHandler() {
444465
protected void configureViewResolvers(ViewResolverRegistry registry) {
445466
}
446467

447-
protected final Map<String, CorsConfiguration> getCorsConfigurations() {
448-
if (this.corsConfigurations == null) {
449-
CorsRegistry registry = new CorsRegistry();
450-
addCorsMappings(registry);
451-
this.corsConfigurations = registry.getCorsConfigurations();
452-
}
453-
return this.corsConfigurations;
454-
}
455-
456-
/**
457-
* Override this method to configure cross origin requests processing.
458-
* @see CorsRegistry
459-
*/
460-
protected void addCorsMappings(CorsRegistry registry) {
461-
}
462-
463468

464469
private static final class EmptyHandlerMapping extends AbstractHandlerMapping {
465470

spring-web-reactive/src/main/java/org/springframework/web/reactive/handler/AbstractHandlerMapping.java

+41-34
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,10 @@ public abstract class AbstractHandlerMapping extends ApplicationObjectSupport
5151

5252
private PathMatcher pathMatcher = new AntPathMatcher();
5353

54-
protected CorsProcessor corsProcessor = new DefaultCorsProcessor();
54+
private final UrlBasedCorsConfigurationSource globalCorsConfigSource = new UrlBasedCorsConfigurationSource();
55+
56+
private CorsProcessor corsProcessor = new DefaultCorsProcessor();
5557

56-
protected final UrlBasedCorsConfigurationSource corsConfigSource = new UrlBasedCorsConfigurationSource();
5758

5859
/**
5960
* Specify the order value for this HandlerMapping bean.
@@ -104,7 +105,7 @@ public HttpRequestPathHelper getPathHelper() {
104105
public void setPathMatcher(PathMatcher pathMatcher) {
105106
Assert.notNull(pathMatcher, "PathMatcher must not be null");
106107
this.pathMatcher = pathMatcher;
107-
this.corsConfigSource.setPathMatcher(pathMatcher);
108+
this.globalCorsConfigSource.setPathMatcher(pathMatcher);
108109
}
109110

110111
/**
@@ -115,9 +116,26 @@ public PathMatcher getPathMatcher() {
115116
return this.pathMatcher;
116117
}
117118

119+
/**
120+
* Set "global" CORS configuration based on URL patterns. By default the
121+
* first matching URL pattern is combined with handler-level CORS
122+
* configuration if any.
123+
*/
124+
public void setCorsConfigurations(Map<String, CorsConfiguration> corsConfigurations) {
125+
this.globalCorsConfigSource.setCorsConfigurations(corsConfigurations);
126+
}
127+
128+
/**
129+
* Return the "global" CORS configuration.
130+
*/
131+
public Map<String, CorsConfiguration> getCorsConfigurations() {
132+
return this.globalCorsConfigSource.getCorsConfigurations();
133+
}
134+
118135
/**
119136
* Configure a custom {@link CorsProcessor} to use to apply the matched
120-
* {@link CorsConfiguration} for a request. By default {@link DefaultCorsProcessor} is used.
137+
* {@link CorsConfiguration} for a request.
138+
* <p>By default an instance of {@link DefaultCorsProcessor} is used.
121139
*/
122140
public void setCorsProcessor(CorsProcessor corsProcessor) {
123141
Assert.notNull(corsProcessor, "CorsProcessor must not be null");
@@ -131,46 +149,35 @@ public CorsProcessor getCorsProcessor() {
131149
return this.corsProcessor;
132150
}
133151

134-
/**
135-
* Set "global" CORS configuration based on URL patterns. By default the first
136-
* matching URL pattern is combined with the CORS configuration for the
137-
* handler, if any.
138-
*/
139-
public void setCorsConfigurations(Map<String, CorsConfiguration> corsConfigurations) {
140-
this.corsConfigSource.setCorsConfigurations(corsConfigurations);
152+
153+
protected Object processCorsRequest(ServerWebExchange exchange, Object handler) {
154+
if (CorsUtils.isCorsRequest(exchange.getRequest())) {
155+
CorsConfiguration configA = this.globalCorsConfigSource.getCorsConfiguration(exchange);
156+
CorsConfiguration configB = getCorsConfiguration(handler, exchange);
157+
CorsConfiguration config = (configA != null ? configA.combine(configB) : configB);
158+
159+
if (!getCorsProcessor().processRequest(config, exchange) ||
160+
CorsUtils.isPreFlightRequest(exchange.getRequest())) {
161+
return REQUEST_HANDLED_HANDLER;
162+
}
163+
}
164+
return handler;
141165
}
142166

143167
/**
144-
* Get the CORS configuration.
168+
* Retrieve the CORS configuration for the given handler.
169+
* @param handler the handler to check (never {@code null}).
170+
* @param exchange the current exchange
171+
* @return the CORS configuration for the handler or {@code null}.
145172
*/
146-
public Map<String, CorsConfiguration> getCorsConfigurations() {
147-
return this.corsConfigSource.getCorsConfigurations();
148-
}
149-
150173
protected CorsConfiguration getCorsConfiguration(Object handler, ServerWebExchange exchange) {
151174
if (handler != null && handler instanceof CorsConfigurationSource) {
152175
return ((CorsConfigurationSource) handler).getCorsConfiguration(exchange);
153176
}
154177
return null;
155178
}
156179

157-
protected Object processCorsRequest(ServerWebExchange exchange, Object handler) {
158-
if (CorsUtils.isCorsRequest(exchange.getRequest())) {
159-
CorsConfiguration globalConfig = this.corsConfigSource.getCorsConfiguration(exchange);
160-
CorsConfiguration handlerConfig = getCorsConfiguration(handler, exchange);
161-
CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
162-
if (!corsProcessor.processRequest(config, exchange) || CorsUtils.isPreFlightRequest(exchange.getRequest())) {
163-
return new NoOpHandler();
164-
}
165-
}
166-
return handler;
167-
}
168180

169-
private class NoOpHandler implements WebHandler {
170-
@Override
171-
public Mono<Void> handle(ServerWebExchange exchange) {
172-
return Mono.empty();
173-
}
174-
}
181+
private static final WebHandler REQUEST_HANDLED_HANDLER = exchange -> Mono.empty();
175182

176-
}
183+
}

spring-web-reactive/src/main/java/org/springframework/web/reactive/handler/AbstractUrlHandlerMapping.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ public final Map<String, Object> getHandlerMap() {
9898
@Override
9999
public Mono<Object> getHandler(ServerWebExchange exchange) {
100100
String lookupPath = getPathHelper().getLookupPathForRequest(exchange);
101-
Object handler = null;
101+
Object handler;
102102
try {
103103
handler = lookupHandler(lookupPath, exchange);
104104
handler = processCorsRequest(exchange, handler);

spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/AbstractHandlerMethodMapping.java

+10-10
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,11 @@
2929
import java.util.concurrent.ConcurrentHashMap;
3030
import java.util.concurrent.locks.ReentrantReadWriteLock;
3131

32-
import javax.servlet.http.HttpServletRequest;
33-
3432
import reactor.core.publisher.Mono;
3533

3634
import org.springframework.aop.support.AopUtils;
3735
import org.springframework.beans.factory.InitializingBean;
3836
import org.springframework.core.MethodIntrospector;
39-
import org.springframework.http.server.reactive.ServerHttpRequest;
4037
import org.springframework.util.Assert;
4138
import org.springframework.util.ClassUtils;
4239
import org.springframework.util.LinkedMultiValueMap;
@@ -75,8 +72,13 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
7572
*/
7673
private static final String SCOPED_TARGET_NAME_PREFIX = "scopedTarget.";
7774

75+
/**
76+
* HandlerMethod to return on a pre-flight request match when the request
77+
* mappings are more nuanced than the access control headers.
78+
*/
7879
private static final HandlerMethod PREFLIGHT_AMBIGUOUS_MATCH =
79-
new HandlerMethod(new EmptyHandler(), ClassUtils.getMethod(EmptyHandler.class, "handle"));
80+
new HandlerMethod(new PreFlightAmbiguousMatchHandler(),
81+
ClassUtils.getMethod(PreFlightAmbiguousMatchHandler.class, "handle"));
8082

8183
private static final CorsConfiguration ALLOW_CORS_CONFIG = new CorsConfiguration();
8284

@@ -372,12 +374,10 @@ protected CorsConfiguration getCorsConfiguration(Object handler, ServerWebExchan
372374
if (handler instanceof HandlerMethod) {
373375
HandlerMethod handlerMethod = (HandlerMethod) handler;
374376
if (handlerMethod.equals(PREFLIGHT_AMBIGUOUS_MATCH)) {
375-
return AbstractHandlerMethodMapping.ALLOW_CORS_CONFIG;
376-
}
377-
else {
378-
CorsConfiguration corsConfigFromMethod = this.mappingRegistry.getCorsConfiguration(handlerMethod);
379-
corsConfig = (corsConfig != null ? corsConfig.combine(corsConfigFromMethod) : corsConfigFromMethod);
377+
return ALLOW_CORS_CONFIG;
380378
}
379+
CorsConfiguration methodConfig = this.mappingRegistry.getCorsConfiguration(handlerMethod);
380+
corsConfig = (corsConfig != null ? corsConfig.combine(methodConfig) : methodConfig);
381381
}
382382
return corsConfig;
383383
}
@@ -625,7 +625,7 @@ public int compare(Match match1, Match match2) {
625625
}
626626
}
627627

628-
private static class EmptyHandler {
628+
private static class PreFlightAmbiguousMatchHandler {
629629

630630
public void handle() {
631631
throw new UnsupportedOperationException("not implemented");

spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestMappingHandlerMapping.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,8 @@ protected String[] resolveEmbeddedValuesInPatterns(String[] patterns) {
282282
@Override
283283
protected CorsConfiguration initCorsConfiguration(Object handler, Method method, RequestMappingInfo mappingInfo) {
284284
HandlerMethod handlerMethod = createHandlerMethod(handler, method);
285-
CrossOrigin typeAnnotation = AnnotatedElementUtils.findMergedAnnotation(handlerMethod.getBeanType(), CrossOrigin.class);
285+
Class<?> beanType = handlerMethod.getBeanType();
286+
CrossOrigin typeAnnotation = AnnotatedElementUtils.findMergedAnnotation(beanType, CrossOrigin.class);
286287
CrossOrigin methodAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, CrossOrigin.class);
287288

288289
if (typeAnnotation == null && methodAnnotation == null) {
@@ -310,6 +311,7 @@ protected CorsConfiguration initCorsConfiguration(Object handler, Method method,
310311
if (config.getMaxAge() == null) {
311312
config.setMaxAge(CrossOrigin.DEFAULT_MAX_AGE);
312313
}
314+
313315
return config;
314316
}
315317

0 commit comments

Comments
 (0)