diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/CorsRegistration.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/CorsRegistration.java index eb0bf1534e..94a745e970 100644 --- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/CorsRegistration.java +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/CorsRegistration.java @@ -24,20 +24,16 @@ import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.cors.CorsConfiguration; /** - * {@code CorsRegistration} assists with the creation of a - * {@link CorsConfiguration} instance mapped to a path pattern. + * Assists with the creation of a {@link CorsConfiguration} instance mapped to + * a path pattern. * - *

If no path pattern is specified, cross-origin request handling is - * mapped to {@code "/**"}. - * - *

By default, all origins, all headers, credentials and {@code GET}, - * {@code HEAD}, and {@code POST} methods are allowed, and the max age is - * set to 30 minutes. + *

If no path pattern is specified, by default cross-origin request handling + * is mapped to {@code "/**"}. Also by default, all origins, headers, + * credentials and {@code GET}, {@code HEAD}, and {@code POST} methods are + * allowed, while the max age is set to 30 minutes. * * @author Sebastien Deleuze - * @author Sam Brannen * @since 5.0 - * @see CorsConfiguration * @see CorsRegistry */ public class CorsRegistration { diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/CorsRegistry.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/CorsRegistry.java index f7f11e7a1d..67b3a06d33 100644 --- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/CorsRegistry.java +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/CorsRegistry.java @@ -58,4 +58,5 @@ public class CorsRegistry { } return configs; } + } diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/WebReactiveConfiguration.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/WebReactiveConfiguration.java index 2a92ab463a..a67215de55 100644 --- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/WebReactiveConfiguration.java +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/WebReactiveConfiguration.java @@ -45,6 +45,8 @@ import org.springframework.http.codec.DecoderHttpMessageReader; import org.springframework.http.codec.EncoderHttpMessageWriter; import org.springframework.http.codec.HttpMessageReader; import org.springframework.http.codec.HttpMessageWriter; +import org.springframework.http.codec.Jackson2ServerHttpMessageReader; +import org.springframework.http.codec.Jackson2ServerHttpMessageWriter; import org.springframework.http.codec.ResourceHttpMessageWriter; import org.springframework.http.codec.ServerSentEventHttpMessageWriter; import org.springframework.http.codec.json.Jackson2JsonDecoder; @@ -61,8 +63,6 @@ import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuild import org.springframework.web.reactive.handler.AbstractHandlerMapping; import org.springframework.web.reactive.result.SimpleHandlerAdapter; import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver; -import org.springframework.http.codec.Jackson2ServerHttpMessageReader; -import org.springframework.http.codec.Jackson2ServerHttpMessageWriter; import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerAdapter; import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerMapping; import org.springframework.web.reactive.result.method.annotation.ResponseBodyResultHandler; @@ -90,14 +90,14 @@ public class WebReactiveConfiguration implements ApplicationContextAware { ClassUtils.isPresent("javax.xml.bind.Binder", WebReactiveConfiguration.class.getClassLoader()); + private Map corsConfigurations; + private PathMatchConfigurer pathMatchConfigurer; private List> messageReaders; private List> messageWriters; - private Map corsConfigurations; - private ApplicationContext applicationContext; @@ -171,6 +171,26 @@ public class WebReactiveConfiguration implements ApplicationContextAware { protected void configureRequestedContentTypeResolver(RequestedContentTypeResolverBuilder builder) { } + /** + * Callback for building the global CORS configuration. This method is final. + * Use {@link #addCorsMappings(CorsRegistry)} to customize the CORS conifg. + */ + protected final Map getCorsConfigurations() { + if (this.corsConfigurations == null) { + CorsRegistry registry = new CorsRegistry(); + addCorsMappings(registry); + this.corsConfigurations = registry.getCorsConfigurations(); + } + return this.corsConfigurations; + } + + /** + * Override this method to configure cross origin requests processing. + * @see CorsRegistry + */ + protected void addCorsMappings(CorsRegistry registry) { + } + /** * Callback for building the {@link PathMatchConfigurer}. This method is * final, use {@link #configurePathMatching} to customize path matching. @@ -209,6 +229,7 @@ public class WebReactiveConfiguration implements ApplicationContextAware { if (pathMatchConfigurer.getPathHelper() != null) { handlerMapping.setPathHelper(pathMatchConfigurer.getPathHelper()); } + } else { handlerMapping = new EmptyHandlerMapping(); @@ -444,22 +465,6 @@ public class WebReactiveConfiguration implements ApplicationContextAware { protected void configureViewResolvers(ViewResolverRegistry registry) { } - protected final Map getCorsConfigurations() { - if (this.corsConfigurations == null) { - CorsRegistry registry = new CorsRegistry(); - addCorsMappings(registry); - this.corsConfigurations = registry.getCorsConfigurations(); - } - return this.corsConfigurations; - } - - /** - * Override this method to configure cross origin requests processing. - * @see CorsRegistry - */ - protected void addCorsMappings(CorsRegistry registry) { - } - private static final class EmptyHandlerMapping extends AbstractHandlerMapping { diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/handler/AbstractHandlerMapping.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/handler/AbstractHandlerMapping.java index b009b23cc7..680d20a48f 100644 --- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/handler/AbstractHandlerMapping.java +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/handler/AbstractHandlerMapping.java @@ -51,9 +51,10 @@ public abstract class AbstractHandlerMapping extends ApplicationObjectSupport private PathMatcher pathMatcher = new AntPathMatcher(); - protected CorsProcessor corsProcessor = new DefaultCorsProcessor(); + private final UrlBasedCorsConfigurationSource globalCorsConfigSource = new UrlBasedCorsConfigurationSource(); + + private CorsProcessor corsProcessor = new DefaultCorsProcessor(); - protected final UrlBasedCorsConfigurationSource corsConfigSource = new UrlBasedCorsConfigurationSource(); /** * Specify the order value for this HandlerMapping bean. @@ -104,7 +105,7 @@ public abstract class AbstractHandlerMapping extends ApplicationObjectSupport public void setPathMatcher(PathMatcher pathMatcher) { Assert.notNull(pathMatcher, "PathMatcher must not be null"); this.pathMatcher = pathMatcher; - this.corsConfigSource.setPathMatcher(pathMatcher); + this.globalCorsConfigSource.setPathMatcher(pathMatcher); } /** @@ -115,9 +116,26 @@ public abstract class AbstractHandlerMapping extends ApplicationObjectSupport return this.pathMatcher; } + /** + * Set "global" CORS configuration based on URL patterns. By default the + * first matching URL pattern is combined with handler-level CORS + * configuration if any. + */ + public void setCorsConfigurations(Map corsConfigurations) { + this.globalCorsConfigSource.setCorsConfigurations(corsConfigurations); + } + + /** + * Return the "global" CORS configuration. + */ + public Map getCorsConfigurations() { + return this.globalCorsConfigSource.getCorsConfigurations(); + } + /** * Configure a custom {@link CorsProcessor} to use to apply the matched - * {@link CorsConfiguration} for a request. By default {@link DefaultCorsProcessor} is used. + * {@link CorsConfiguration} for a request. + *

By default an instance of {@link DefaultCorsProcessor} is used. */ public void setCorsProcessor(CorsProcessor corsProcessor) { Assert.notNull(corsProcessor, "CorsProcessor must not be null"); @@ -131,22 +149,27 @@ public abstract class AbstractHandlerMapping extends ApplicationObjectSupport return this.corsProcessor; } - /** - * Set "global" CORS configuration based on URL patterns. By default the first - * matching URL pattern is combined with the CORS configuration for the - * handler, if any. - */ - public void setCorsConfigurations(Map corsConfigurations) { - this.corsConfigSource.setCorsConfigurations(corsConfigurations); + + protected Object processCorsRequest(ServerWebExchange exchange, Object handler) { + if (CorsUtils.isCorsRequest(exchange.getRequest())) { + CorsConfiguration configA = this.globalCorsConfigSource.getCorsConfiguration(exchange); + CorsConfiguration configB = getCorsConfiguration(handler, exchange); + CorsConfiguration config = (configA != null ? configA.combine(configB) : configB); + + if (!getCorsProcessor().processRequest(config, exchange) || + CorsUtils.isPreFlightRequest(exchange.getRequest())) { + return REQUEST_HANDLED_HANDLER; + } + } + return handler; } /** - * Get the CORS configuration. + * Retrieve the CORS configuration for the given handler. + * @param handler the handler to check (never {@code null}). + * @param exchange the current exchange + * @return the CORS configuration for the handler or {@code null}. */ - public Map getCorsConfigurations() { - return this.corsConfigSource.getCorsConfigurations(); - } - protected CorsConfiguration getCorsConfiguration(Object handler, ServerWebExchange exchange) { if (handler != null && handler instanceof CorsConfigurationSource) { return ((CorsConfigurationSource) handler).getCorsConfiguration(exchange); @@ -154,23 +177,7 @@ public abstract class AbstractHandlerMapping extends ApplicationObjectSupport return null; } - protected Object processCorsRequest(ServerWebExchange exchange, Object handler) { - if (CorsUtils.isCorsRequest(exchange.getRequest())) { - CorsConfiguration globalConfig = this.corsConfigSource.getCorsConfiguration(exchange); - CorsConfiguration handlerConfig = getCorsConfiguration(handler, exchange); - CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig); - if (!corsProcessor.processRequest(config, exchange) || CorsUtils.isPreFlightRequest(exchange.getRequest())) { - return new NoOpHandler(); - } - } - return handler; - } - private class NoOpHandler implements WebHandler { - @Override - public Mono handle(ServerWebExchange exchange) { - return Mono.empty(); - } - } + private static final WebHandler REQUEST_HANDLED_HANDLER = exchange -> Mono.empty(); -} +} \ No newline at end of file diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/handler/AbstractUrlHandlerMapping.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/handler/AbstractUrlHandlerMapping.java index bb87cee707..f4ec65af55 100644 --- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/handler/AbstractUrlHandlerMapping.java +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/handler/AbstractUrlHandlerMapping.java @@ -98,7 +98,7 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping { @Override public Mono getHandler(ServerWebExchange exchange) { String lookupPath = getPathHelper().getLookupPathForRequest(exchange); - Object handler = null; + Object handler; try { handler = lookupHandler(lookupPath, exchange); handler = processCorsRequest(exchange, handler); diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/AbstractHandlerMethodMapping.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/AbstractHandlerMethodMapping.java index 063e5fc3bc..312b7d69ac 100644 --- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/AbstractHandlerMethodMapping.java +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/AbstractHandlerMethodMapping.java @@ -29,14 +29,11 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.ReentrantReadWriteLock; -import javax.servlet.http.HttpServletRequest; - import reactor.core.publisher.Mono; import org.springframework.aop.support.AopUtils; import org.springframework.beans.factory.InitializingBean; import org.springframework.core.MethodIntrospector; -import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.LinkedMultiValueMap; @@ -75,8 +72,13 @@ public abstract class AbstractHandlerMethodMapping extends AbstractHandlerMap */ private static final String SCOPED_TARGET_NAME_PREFIX = "scopedTarget."; + /** + * HandlerMethod to return on a pre-flight request match when the request + * mappings are more nuanced than the access control headers. + */ private static final HandlerMethod PREFLIGHT_AMBIGUOUS_MATCH = - new HandlerMethod(new EmptyHandler(), ClassUtils.getMethod(EmptyHandler.class, "handle")); + new HandlerMethod(new PreFlightAmbiguousMatchHandler(), + ClassUtils.getMethod(PreFlightAmbiguousMatchHandler.class, "handle")); private static final CorsConfiguration ALLOW_CORS_CONFIG = new CorsConfiguration(); @@ -372,12 +374,10 @@ public abstract class AbstractHandlerMethodMapping extends AbstractHandlerMap if (handler instanceof HandlerMethod) { HandlerMethod handlerMethod = (HandlerMethod) handler; if (handlerMethod.equals(PREFLIGHT_AMBIGUOUS_MATCH)) { - return AbstractHandlerMethodMapping.ALLOW_CORS_CONFIG; - } - else { - CorsConfiguration corsConfigFromMethod = this.mappingRegistry.getCorsConfiguration(handlerMethod); - corsConfig = (corsConfig != null ? corsConfig.combine(corsConfigFromMethod) : corsConfigFromMethod); + return ALLOW_CORS_CONFIG; } + CorsConfiguration methodConfig = this.mappingRegistry.getCorsConfiguration(handlerMethod); + corsConfig = (corsConfig != null ? corsConfig.combine(methodConfig) : methodConfig); } return corsConfig; } @@ -625,7 +625,7 @@ public abstract class AbstractHandlerMethodMapping extends AbstractHandlerMap } } - private static class EmptyHandler { + private static class PreFlightAmbiguousMatchHandler { public void handle() { throw new UnsupportedOperationException("not implemented"); diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestMappingHandlerMapping.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestMappingHandlerMapping.java index e87cf47855..439a5c78b9 100644 --- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestMappingHandlerMapping.java +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestMappingHandlerMapping.java @@ -282,7 +282,8 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi @Override protected CorsConfiguration initCorsConfiguration(Object handler, Method method, RequestMappingInfo mappingInfo) { HandlerMethod handlerMethod = createHandlerMethod(handler, method); - CrossOrigin typeAnnotation = AnnotatedElementUtils.findMergedAnnotation(handlerMethod.getBeanType(), CrossOrigin.class); + Class beanType = handlerMethod.getBeanType(); + CrossOrigin typeAnnotation = AnnotatedElementUtils.findMergedAnnotation(beanType, CrossOrigin.class); CrossOrigin methodAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, CrossOrigin.class); if (typeAnnotation == null && methodAnnotation == null) { @@ -310,6 +311,7 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi if (config.getMaxAge() == null) { config.setMaxAge(CrossOrigin.DEFAULT_MAX_AGE); } + return config; } diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/handler/CorsAbstractUrlHandlerMappingTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/handler/CorsUrlHandlerMappingTests.java similarity index 55% rename from spring-web-reactive/src/test/java/org/springframework/web/reactive/handler/CorsAbstractUrlHandlerMappingTests.java rename to spring-web-reactive/src/test/java/org/springframework/web/reactive/handler/CorsUrlHandlerMappingTests.java index 3ed1bc6efc..05f0ad92aa 100644 --- a/spring-web-reactive/src/test/java/org/springframework/web/reactive/handler/CorsAbstractUrlHandlerMappingTests.java +++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/handler/CorsUrlHandlerMappingTests.java @@ -15,7 +15,6 @@ */ package org.springframework.web.reactive.handler; -import java.net.URISyntaxException; import java.util.Collections; import static org.junit.Assert.*; @@ -23,9 +22,6 @@ import static org.junit.Assert.assertSame; import org.junit.Before; import org.junit.Test; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.server.reactive.ServerHttpRequest; @@ -44,132 +40,111 @@ import org.springframework.web.server.session.WebSessionManager; * @author Sebastien Deleuze * @author Rossen Stoyanchev */ -public class CorsAbstractUrlHandlerMappingTests { +public class CorsUrlHandlerMappingTests { - private AnnotationConfigApplicationContext wac; + private AbstractUrlHandlerMapping handlerMapping; - private TestUrlHandlerMapping handlerMapping; + private Object welcomeController = new Object(); - private Object mainController; + private CorsAwareHandler corsController = new CorsAwareHandler(); - private CorsAwareHandler corsConfigurationSourceController; @Before public void setup() { - wac = new AnnotationConfigApplicationContext(); - wac.register(WebConfig.class); - wac.refresh(); - - handlerMapping = (TestUrlHandlerMapping) wac.getBean("handlerMapping"); - mainController = wac.getBean("mainController"); - corsConfigurationSourceController = (CorsAwareHandler) wac.getBean("corsConfigurationSourceController"); + this.handlerMapping = new AbstractUrlHandlerMapping() {}; + this.handlerMapping.setUseTrailingSlashMatch(true); + this.handlerMapping.registerHandler("/welcome.html", this.welcomeController); + this.handlerMapping.registerHandler("/cors.html", this.corsController); } + @Test public void actualRequestWithoutCorsConfigurationProvider() throws Exception { - ServerWebExchange exchange = createExchange(HttpMethod.GET, "/welcome.html", "http://domain2.com", "GET"); - Object actual = handlerMapping.getHandler(exchange).block(); + String origin = "http://domain2.com"; + ServerWebExchange exchange = createExchange(HttpMethod.GET, "/welcome.html", origin, "GET"); + Object actual = this.handlerMapping.getHandler(exchange).block(); + assertNotNull(actual); - assertSame(mainController, actual); + assertSame(this.welcomeController, actual); } @Test public void preflightRequestWithoutCorsConfigurationProvider() throws Exception { - ServerWebExchange exchange = createExchange(HttpMethod.OPTIONS, "/welcome.html", "http://domain2.com", "GET"); - Object actual = handlerMapping.getHandler(exchange).block(); + String origin = "http://domain2.com"; + ServerWebExchange exchange = createExchange(HttpMethod.OPTIONS, "/welcome.html", origin, "GET"); + Object actual = this.handlerMapping.getHandler(exchange).block(); + assertNotNull(actual); - assertEquals("NoOpHandler", actual.getClass().getSimpleName()); + assertNotSame(this.welcomeController, actual); assertNull(exchange.getResponse().getHeaders().getFirst(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)); } @Test - public void actualRequestWithCorsConfigurationProvider() throws Exception { - ServerWebExchange exchange = createExchange(HttpMethod.GET, "/cors.html", "http://domain2.com", "GET"); - Object actual = handlerMapping.getHandler(exchange).block(); + public void actualRequestWithCorsAwareHandler() throws Exception { + String origin = "http://domain2.com"; + ServerWebExchange exchange = createExchange(HttpMethod.GET, "/cors.html", origin, "GET"); + Object actual = this.handlerMapping.getHandler(exchange).block(); + assertNotNull(actual); - assertSame(corsConfigurationSourceController, actual); - CorsConfiguration config = ((CorsConfigurationSource)actual).getCorsConfiguration(createExchange(HttpMethod.GET, "", "","")); - assertNotNull(config); - assertArrayEquals(config.getAllowedOrigins().toArray(), new String[]{"*"}); + assertSame(this.corsController, actual); assertEquals("*", exchange.getResponse().getHeaders().getFirst(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)); } @Test - public void preflightRequestWithCorsConfigurationProvider() throws Exception { - ServerWebExchange exchange = createExchange(HttpMethod.OPTIONS, "/cors.html", "http://domain2.com", "GET"); - Object actual = handlerMapping.getHandler(exchange).block(); + public void preFlightWithCorsAwareHandler() throws Exception { + String origin = "http://domain2.com"; + ServerWebExchange exchange = createExchange(HttpMethod.OPTIONS, "/cors.html", origin, "GET"); + Object actual = this.handlerMapping.getHandler(exchange).block(); + assertNotNull(actual); - assertEquals("NoOpHandler", actual.getClass().getSimpleName()); + assertNotSame(this.corsController, actual); assertEquals("*", exchange.getResponse().getHeaders().getFirst(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)); } @Test - public void actualRequestWithMappedCorsConfiguration() throws Exception { + public void actualRequestWithGlobalCorsConfig() throws Exception { CorsConfiguration mappedConfig = new CorsConfiguration(); mappedConfig.addAllowedOrigin("*"); this.handlerMapping.setCorsConfigurations(Collections.singletonMap("/welcome.html", mappedConfig)); - ServerWebExchange exchange = createExchange(HttpMethod.GET, "/welcome.html", "http://domain2.com", "GET"); - Object actual = handlerMapping.getHandler(exchange).block(); + String origin = "http://domain2.com"; + ServerWebExchange exchange = createExchange(HttpMethod.GET, "/welcome.html", origin, "GET"); + Object actual = this.handlerMapping.getHandler(exchange).block(); + assertNotNull(actual); - assertSame(mainController, actual); + assertSame(this.welcomeController, actual); assertEquals("*", exchange.getResponse().getHeaders().getFirst(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)); } @Test - public void preflightRequestWithMappedCorsConfiguration() throws Exception { + public void preFlightRequestWithGlobalCorsConfig() throws Exception { CorsConfiguration mappedConfig = new CorsConfiguration(); mappedConfig.addAllowedOrigin("*"); this.handlerMapping.setCorsConfigurations(Collections.singletonMap("/welcome.html", mappedConfig)); - ServerWebExchange exchange = createExchange(HttpMethod.OPTIONS, "/welcome.html", "http://domain2.com", "GET"); - Object actual = handlerMapping.getHandler(exchange).block(); + String origin = "http://domain2.com"; + ServerWebExchange exchange = createExchange(HttpMethod.OPTIONS, "/welcome.html", origin, "GET"); + Object actual = this.handlerMapping.getHandler(exchange).block(); + assertNotNull(actual); - assertEquals("NoOpHandler", actual.getClass().getSimpleName()); + assertNotSame(this.welcomeController, actual); assertEquals("*", exchange.getResponse().getHeaders().getFirst(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)); } private ServerWebExchange createExchange(HttpMethod method, String path, String origin, - String accessControlRequestMethod) throws URISyntaxException { + String accessControlRequestMethod) { ServerHttpRequest request = new MockServerHttpRequest(method, "http://localhost" + path); request.getHeaders().add(HttpHeaders.ORIGIN, origin); request.getHeaders().add(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, accessControlRequestMethod); + MockServerHttpResponse response = new MockServerHttpResponse(); WebSessionManager sessionManager = new MockWebSessionManager(); - return new DefaultServerWebExchange(request, new MockServerHttpResponse(), sessionManager); + return new DefaultServerWebExchange(request, response, sessionManager); } - @Configuration - static class WebConfig { - - @Bean @SuppressWarnings("unused") - public TestUrlHandlerMapping handlerMapping() { - TestUrlHandlerMapping hm = new TestUrlHandlerMapping(); - hm.setUseTrailingSlashMatch(true); - hm.registerHandler("/welcome.html", mainController()); - hm.registerHandler("/cors.html", corsConfigurationSourceController()); - return hm; - } - - @Bean - public Object mainController() { - return new Object(); - } - - @Bean - public CorsAwareHandler corsConfigurationSourceController() { - return new CorsAwareHandler(); - } - - } - - static class TestUrlHandlerMapping extends AbstractUrlHandlerMapping { - - } - - static class CorsAwareHandler implements CorsConfigurationSource { + private class CorsAwareHandler implements CorsConfigurationSource { @Override public CorsConfiguration getCorsConfiguration(ServerWebExchange exchange) { diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/AbstractRequestMappingIntegrationTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/AbstractRequestMappingIntegrationTests.java index b2aa35a3bf..e6f373a9de 100644 --- a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/AbstractRequestMappingIntegrationTests.java +++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/AbstractRequestMappingIntegrationTests.java @@ -16,9 +16,13 @@ package org.springframework.web.reactive.result.method.annotation; import java.net.URI; +import java.util.Collections; +import java.util.List; +import java.util.Map; import org.springframework.context.ApplicationContext; import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.RequestEntity; import org.springframework.http.ResponseEntity; @@ -30,20 +34,24 @@ import org.springframework.web.server.adapter.WebHttpHandlerBuilder; import org.springframework.web.server.handler.ResponseStatusExceptionHandler; import static org.springframework.http.RequestEntity.get; +import static org.springframework.http.RequestEntity.options; +import static org.springframework.http.RequestEntity.post; /** + * Base class for integration tests with {@code @RequestMapping methods}. * * @author Rossen Stoyanchev */ public abstract class AbstractRequestMappingIntegrationTests extends AbstractHttpHandlerIntegrationTests { - private ApplicationContext applicationContext; - private RestTemplate restTemplate = new RestTemplate(); + private ApplicationContext applicationContext; + @Override protected HttpHandler createHttpHandler() { + this.restTemplate = initRestTemplate(); this.applicationContext = initApplicationContext(); return WebHttpHandlerBuilder .webHandler(new DispatcherHandler(this.applicationContext)) @@ -53,49 +61,98 @@ public abstract class AbstractRequestMappingIntegrationTests extends AbstractHtt protected abstract ApplicationContext initApplicationContext(); + protected RestTemplate initRestTemplate() { + return new RestTemplate(); + } - ApplicationContext getApplicationContext() { + protected ApplicationContext getApplicationContext() { return this.applicationContext; } - RestTemplate getRestTemplate() { + protected RestTemplate getRestTemplate() { return this.restTemplate; } - ResponseEntity performGet(String url, MediaType out, - Class type) throws Exception { - - return this.restTemplate.exchange(prepareGet(url, out), type); + ResponseEntity performGet(String url, MediaType out, Class type) throws Exception { + HttpHeaders headers = new HttpHeaders(); + headers.setAccept(Collections.singletonList(out)); + return getRestTemplate().exchange(prepareGet(url, headers), type); } - ResponseEntity performGet(String url, MediaType out, - ParameterizedTypeReference type) throws Exception { + ResponseEntity performGet(String url, HttpHeaders headers, Class type) throws Exception { + return getRestTemplate().exchange(prepareGet(url, headers), type); + } - return this.restTemplate.exchange(prepareGet(url, out), type); + ResponseEntity performGet(String url, MediaType out, ParameterizedTypeReference type) + throws Exception { + + HttpHeaders headers = new HttpHeaders(); + headers.setAccept(Collections.singletonList(out)); + return this.restTemplate.exchange(prepareGet(url, headers), type); + } + + ResponseEntity performOptions(String url, HttpHeaders headers, Class type) + throws Exception { + + return getRestTemplate().exchange(prepareOptions(url, headers), type); + } + + ResponseEntity performPost(String url, MediaType in, Object body, MediaType out, Class type) + throws Exception { + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(in); + if (out != null) { + headers.setAccept(Collections.singletonList(out)); + } + return getRestTemplate().exchange(preparePost(url, headers, body), type); + } + + ResponseEntity performPost(String url, HttpHeaders headers, Object body, + Class type) throws Exception { + + return getRestTemplate().exchange(preparePost(url, headers, body), type); } ResponseEntity performPost(String url, MediaType in, Object body, MediaType out, - Class type) throws Exception { + ParameterizedTypeReference type) throws Exception { - return this.restTemplate.exchange(preparePost(url, in, body, out), type); + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(in); + if (out != null) { + headers.setAccept(Collections.singletonList(out)); + } + return getRestTemplate().exchange(preparePost(url, headers, body), type); } - ResponseEntity performPost(String url, MediaType in, Object body, - MediaType out, ParameterizedTypeReference type) throws Exception { - - return this.restTemplate.exchange(preparePost(url, in, body, out), type); - } - - private RequestEntity prepareGet(String url, MediaType accept) throws Exception { + private RequestEntity prepareGet(String url, HttpHeaders headers) throws Exception { URI uri = new URI("http://localhost:" + this.port + url); - return (accept != null ? get(uri).accept(accept).build() : get(uri).build()); + RequestEntity.HeadersBuilder builder = get(uri); + addHeaders(builder, headers); + return builder.build(); } - private RequestEntity preparePost(String url, MediaType in, Object body, MediaType out) throws Exception { + private RequestEntity prepareOptions(String url, HttpHeaders headers) throws Exception { URI uri = new URI("http://localhost:" + this.port + url); - return (out != null ? - RequestEntity.post(uri).contentType(in).accept(out).body(body) : - RequestEntity.post(uri).contentType(in).body(body)); + RequestEntity.HeadersBuilder builder = options(uri); + addHeaders(builder, headers); + return builder.build(); } + + private void addHeaders(RequestEntity.HeadersBuilder builder, HttpHeaders headers) { + for (Map.Entry> entry : headers.entrySet()) { + for (String value : entry.getValue()) { + builder.header(entry.getKey(), value); + } + } + } + + private RequestEntity preparePost(String url, HttpHeaders headers, Object body) throws Exception { + URI uri = new URI("http://localhost:" + this.port + url); + RequestEntity.BodyBuilder builder = post(uri); + addHeaders(builder, headers); + return builder.body(body); + } + } diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/CrossOriginAnnotationIntegrationTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/CrossOriginAnnotationIntegrationTests.java index 1e5a9fdc0f..fcf44e7c5f 100644 --- a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/CrossOriginAnnotationIntegrationTests.java +++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/CrossOriginAnnotationIntegrationTests.java @@ -18,8 +18,7 @@ package org.springframework.web.reactive.result.method.annotation; import java.util.Properties; -import static org.junit.Assert.*; -import static org.junit.Assert.assertArrayEquals; +import org.junit.Before; import org.junit.Test; import org.springframework.context.ApplicationContext; @@ -28,76 +27,82 @@ import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; import org.springframework.core.env.PropertiesPropertySource; -import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.web.bind.annotation.CrossOrigin; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import org.springframework.web.reactive.config.WebReactiveConfiguration; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + /** + * Integration tests with {@code @CrossOrigin} and {@code @RequestMapping} + * annotated handler methods. + * * @author Sebastien Deleuze + * @author Rossen Stoyanchev */ public class CrossOriginAnnotationIntegrationTests extends AbstractRequestMappingIntegrationTests { - // JDK default HTTP client blacklist headers like Origin - private RestTemplate restTemplate = new RestTemplate(new HttpComponentsClientHttpRequestFactory()); + private HttpHeaders headers; + + + @Before + public void setup() throws Exception { + super.setup(); + this.headers = new HttpHeaders(); + this.headers.setOrigin("http://site1.com"); + } @Override protected ApplicationContext initApplicationContext() { - AnnotationConfigApplicationContext wac = new AnnotationConfigApplicationContext(); - wac.register(WebConfig.class); + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + context.register(WebConfig.class); Properties props = new Properties(); props.setProperty("myOrigin", "http://site1.com"); - wac.getEnvironment().getPropertySources().addFirst(new PropertiesPropertySource("ps", props)); - wac.register(PropertySourcesPlaceholderConfigurer.class); - wac.refresh(); - return wac; + context.getEnvironment().getPropertySources().addFirst(new PropertiesPropertySource("ps", props)); + context.register(PropertySourcesPlaceholderConfigurer.class); + context.refresh(); + return context; } @Override - RestTemplate getRestTemplate() { - return this.restTemplate; + protected RestTemplate initRestTemplate() { + // JDK default HTTP client blacklist headers like Origin + return new RestTemplate(new HttpComponentsClientHttpRequestFactory()); } + @Test - public void actualGetRequestWithoutAnnotation() { - HttpHeaders headers = new HttpHeaders(); - headers.add(HttpHeaders.ORIGIN, "http://site1.com"); - HttpEntity requestEntity = new HttpEntity(headers); - ResponseEntity entity = this.restTemplate.exchange(getUrl("/no"), - HttpMethod.GET, requestEntity, String.class); + public void actualGetRequestWithoutAnnotation() throws Exception { + ResponseEntity entity = performGet("/no", this.headers, String.class); assertEquals(HttpStatus.OK, entity.getStatusCode()); assertNull(entity.getHeaders().getAccessControlAllowOrigin()); assertEquals("no", entity.getBody()); } @Test - public void actualPostRequestWithoutAnnotation() { - HttpHeaders headers = new HttpHeaders(); - headers.add(HttpHeaders.ORIGIN, "http://site1.com"); - HttpEntity requestEntity = new HttpEntity(headers); - ResponseEntity entity = this.restTemplate.exchange(getUrl("/no"), - HttpMethod.POST, requestEntity, String.class); + public void actualPostRequestWithoutAnnotation() throws Exception { + ResponseEntity entity = performPost("/no", this.headers, null, String.class); assertEquals(HttpStatus.OK, entity.getStatusCode()); assertNull(entity.getHeaders().getAccessControlAllowOrigin()); assertEquals("no-post", entity.getBody()); } @Test - public void actualRequestWithDefaultAnnotation() { - HttpHeaders headers = new HttpHeaders(); - headers.add(HttpHeaders.ORIGIN, "http://site1.com"); - HttpEntity requestEntity = new HttpEntity(headers); - ResponseEntity entity = this.restTemplate.exchange(getUrl("/default"), - HttpMethod.GET, requestEntity, String.class); + public void actualRequestWithDefaultAnnotation() throws Exception { + ResponseEntity entity = performGet("/default", this.headers, String.class); assertEquals(HttpStatus.OK, entity.getStatusCode()); assertEquals("http://site1.com", entity.getHeaders().getAccessControlAllowOrigin()); assertEquals(true, entity.getHeaders().getAccessControlAllowCredentials()); @@ -105,13 +110,9 @@ public class CrossOriginAnnotationIntegrationTests extends AbstractRequestMappin } @Test - public void preflightRequestWithDefaultAnnotation() { - HttpHeaders headers = new HttpHeaders(); - headers.add(HttpHeaders.ORIGIN, "http://site1.com"); - headers.add(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET"); - HttpEntity requestEntity = new HttpEntity(headers); - ResponseEntity entity = this.restTemplate.exchange(getUrl("/default"), - HttpMethod.OPTIONS, requestEntity, Void.class); + public void preflightRequestWithDefaultAnnotation() throws Exception { + this.headers.add(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET"); + ResponseEntity entity = performOptions("/default", this.headers, Void.class); assertEquals(HttpStatus.OK, entity.getStatusCode()); assertEquals("http://site1.com", entity.getHeaders().getAccessControlAllowOrigin()); assertEquals(1800, entity.getHeaders().getAccessControlMaxAge()); @@ -119,23 +120,17 @@ public class CrossOriginAnnotationIntegrationTests extends AbstractRequestMappin } @Test - public void actualRequestWithDefaultAnnotationAndNoOrigin() { + public void actualRequestWithDefaultAnnotationAndNoOrigin() throws Exception { HttpHeaders headers = new HttpHeaders(); - HttpEntity requestEntity = new HttpEntity(headers); - ResponseEntity entity = this.restTemplate.exchange(getUrl("/default"), - HttpMethod.GET, requestEntity, String.class); + ResponseEntity entity = performGet("/default", headers, String.class); assertEquals(HttpStatus.OK, entity.getStatusCode()); assertNull(entity.getHeaders().getAccessControlAllowOrigin()); assertEquals("default", entity.getBody()); } @Test - public void actualRequestWithCustomizedAnnotation() { - HttpHeaders headers = new HttpHeaders(); - headers.add(HttpHeaders.ORIGIN, "http://site1.com"); - HttpEntity requestEntity = new HttpEntity(headers); - ResponseEntity entity = this.restTemplate.exchange(getUrl("/customized"), - HttpMethod.GET, requestEntity, String.class); + public void actualRequestWithCustomizedAnnotation() throws Exception { + ResponseEntity entity = performGet("/customized", this.headers, String.class); assertEquals(HttpStatus.OK, entity.getStatusCode()); assertEquals("http://site1.com", entity.getHeaders().getAccessControlAllowOrigin()); assertEquals(false, entity.getHeaders().getAccessControlAllowCredentials()); @@ -144,67 +139,54 @@ public class CrossOriginAnnotationIntegrationTests extends AbstractRequestMappin } @Test - public void preflightRequestWithCustomizedAnnotation() { - HttpHeaders headers = new HttpHeaders(); - headers.add(HttpHeaders.ORIGIN, "http://site1.com"); - headers.add(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET"); - headers.add(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS, "header1, header2"); - HttpEntity requestEntity = new HttpEntity(headers); - ResponseEntity entity = this.restTemplate.exchange(getUrl("/customized"), - HttpMethod.OPTIONS, requestEntity, String.class); + public void preflightRequestWithCustomizedAnnotation() throws Exception { + this.headers.add(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET"); + this.headers.add(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS, "header1, header2"); + ResponseEntity entity = performOptions("/customized", this.headers, String.class); + assertEquals(HttpStatus.OK, entity.getStatusCode()); assertEquals("http://site1.com", entity.getHeaders().getAccessControlAllowOrigin()); - assertArrayEquals(new HttpMethod[] {HttpMethod.GET}, entity.getHeaders().getAccessControlAllowMethods().toArray()); + assertArrayEquals(new HttpMethod[] {HttpMethod.GET}, + entity.getHeaders().getAccessControlAllowMethods().toArray()); + assertArrayEquals(new String[] {"header1", "header2"}, + entity.getHeaders().getAccessControlAllowHeaders().toArray()); + assertArrayEquals(new String[] {"header3", "header4"}, + entity.getHeaders().getAccessControlExposeHeaders().toArray()); assertEquals(false, entity.getHeaders().getAccessControlAllowCredentials()); - assertArrayEquals(new String[] {"header1", "header2"}, entity.getHeaders().getAccessControlAllowHeaders().toArray()); - assertArrayEquals(new String[] {"header3", "header4"}, entity.getHeaders().getAccessControlExposeHeaders().toArray()); assertEquals(123, entity.getHeaders().getAccessControlMaxAge()); } @Test - public void customOriginDefinedViaValueAttribute() { - HttpHeaders headers = new HttpHeaders(); - headers.add(HttpHeaders.ORIGIN, "http://site1.com"); - HttpEntity requestEntity = new HttpEntity(headers); - ResponseEntity entity = this.restTemplate.exchange(getUrl("/origin-value-attribute"), - HttpMethod.GET, requestEntity, String.class); + public void customOriginDefinedViaValueAttribute() throws Exception { + ResponseEntity entity = performGet("/origin-value-attribute", this.headers, String.class); assertEquals(HttpStatus.OK, entity.getStatusCode()); assertEquals("http://site1.com", entity.getHeaders().getAccessControlAllowOrigin()); assertEquals("value-attribute", entity.getBody()); } @Test - public void customOriginDefinedViaPlaceholder() { - HttpHeaders headers = new HttpHeaders(); - headers.add(HttpHeaders.ORIGIN, "http://site1.com"); - HttpEntity requestEntity = new HttpEntity(headers); - ResponseEntity entity = this.restTemplate.exchange(getUrl("/origin-placeholder"), - HttpMethod.GET, requestEntity, String.class); + public void customOriginDefinedViaPlaceholder() throws Exception { + ResponseEntity entity = performGet("/origin-placeholder", this.headers, String.class); assertEquals(HttpStatus.OK, entity.getStatusCode()); assertEquals("http://site1.com", entity.getHeaders().getAccessControlAllowOrigin()); assertEquals("placeholder", entity.getBody()); } @Test - public void classLevel() { - HttpHeaders headers = new HttpHeaders(); - headers.add(HttpHeaders.ORIGIN, "http://site1.com"); - HttpEntity requestEntity = new HttpEntity(headers); - - ResponseEntity entity = this.restTemplate.exchange(getUrl("/foo"), - HttpMethod.GET, requestEntity, String.class); + public void classLevel() throws Exception { + ResponseEntity entity = performGet("/foo", this.headers, String.class); assertEquals(HttpStatus.OK, entity.getStatusCode()); assertEquals("*", entity.getHeaders().getAccessControlAllowOrigin()); assertEquals(false, entity.getHeaders().getAccessControlAllowCredentials()); assertEquals("foo", entity.getBody()); - entity = this.restTemplate.exchange(getUrl("/bar"), HttpMethod.GET, requestEntity, String.class); + entity = performGet("/bar", this.headers, String.class); assertEquals(HttpStatus.OK, entity.getStatusCode()); assertEquals("*", entity.getHeaders().getAccessControlAllowOrigin()); assertEquals(false, entity.getHeaders().getAccessControlAllowCredentials()); assertEquals("bar", entity.getBody()); - entity = this.restTemplate.exchange(getUrl("/baz"), HttpMethod.GET, requestEntity, String.class); + entity = performGet("/baz", this.headers, String.class); assertEquals(HttpStatus.OK, entity.getStatusCode()); assertEquals("http://site1.com", entity.getHeaders().getAccessControlAllowOrigin()); assertEquals(true, entity.getHeaders().getAccessControlAllowCredentials()); @@ -213,107 +195,104 @@ public class CrossOriginAnnotationIntegrationTests extends AbstractRequestMappin @Test public void ambiguousHeaderPreflightRequest() throws Exception { - HttpHeaders headers = new HttpHeaders(); - headers.add(HttpHeaders.ORIGIN, "http://site1.com"); - headers.add(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET"); - headers.add(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS, "header1"); - HttpEntity requestEntity = new HttpEntity(headers); - ResponseEntity entity = this.restTemplate.exchange(getUrl("/ambiguous-header"), - HttpMethod.OPTIONS, requestEntity, String.class); + this.headers.add(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET"); + this.headers.add(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS, "header1"); + ResponseEntity entity = performOptions("/ambiguous-header", this.headers, String.class); + assertEquals(HttpStatus.OK, entity.getStatusCode()); assertEquals("http://site1.com", entity.getHeaders().getAccessControlAllowOrigin()); - assertArrayEquals(new HttpMethod[] {HttpMethod.GET}, entity.getHeaders().getAccessControlAllowMethods().toArray()); + assertArrayEquals(new HttpMethod[] {HttpMethod.GET}, + entity.getHeaders().getAccessControlAllowMethods().toArray()); + assertArrayEquals(new String[] {"header1"}, + entity.getHeaders().getAccessControlAllowHeaders().toArray()); assertEquals(true, entity.getHeaders().getAccessControlAllowCredentials()); - assertArrayEquals(new String[] {"header1"}, entity.getHeaders().getAccessControlAllowHeaders().toArray()); } @Test public void ambiguousProducesPreflightRequest() throws Exception { - HttpHeaders headers = new HttpHeaders(); - headers.add(HttpHeaders.ORIGIN, "http://site1.com"); - headers.add(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET"); - HttpEntity requestEntity = new HttpEntity(headers); - ResponseEntity entity = this.restTemplate.exchange(getUrl("/ambiguous-produces"), - HttpMethod.OPTIONS, requestEntity, String.class); + this.headers.add(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET"); + ResponseEntity entity = performOptions("/ambiguous-produces", this.headers, String.class); + assertEquals(HttpStatus.OK, entity.getStatusCode()); assertEquals("http://site1.com", entity.getHeaders().getAccessControlAllowOrigin()); - assertArrayEquals(new HttpMethod[] {HttpMethod.GET}, entity.getHeaders().getAccessControlAllowMethods().toArray()); + assertArrayEquals(new HttpMethod[] {HttpMethod.GET}, + entity.getHeaders().getAccessControlAllowMethods().toArray()); assertEquals(true, entity.getHeaders().getAccessControlAllowCredentials()); } - private String getUrl(String path) { - return "http://localhost:" + this.port + path; - } - @Configuration @ComponentScan(resourcePattern = "**/CrossOriginAnnotationIntegrationTests*") @SuppressWarnings({"unused", "WeakerAccess"}) static class WebConfig extends WebReactiveConfiguration { - } - @RestController + @RestController @SuppressWarnings("unused") private static class MethodLevelController { - @RequestMapping(path = "/no", method = RequestMethod.GET) + @GetMapping("/no") public String noAnnotation() { return "no"; } - @RequestMapping(path = "/no", method = RequestMethod.POST) + @PostMapping("/no") public String noAnnotationPost() { return "no-post"; } @CrossOrigin - @RequestMapping(path = "/default", method = RequestMethod.GET) + @GetMapping("/default") public String defaultAnnotation() { return "default"; } @CrossOrigin - @RequestMapping(path = "/default", method = RequestMethod.GET, params = "q") + @GetMapping(path = "/default", params = "q") public void defaultAnnotationWithParams() { } @CrossOrigin - @RequestMapping(path = "/ambiguous-header", method = RequestMethod.GET, headers = "header1=a") + @GetMapping(path = "/ambiguous-header", headers = "header1=a") public void ambigousHeader1a() { } @CrossOrigin - @RequestMapping(path = "/ambiguous-header", method = RequestMethod.GET, headers = "header1=b") + @GetMapping(path = "/ambiguous-header", headers = "header1=b") public void ambigousHeader1b() { } @CrossOrigin - @RequestMapping(path = "/ambiguous-produces", method = RequestMethod.GET, produces = "application/xml") + @GetMapping(path = "/ambiguous-produces", produces = "application/xml") public String ambigousProducesXml() { return ""; } @CrossOrigin - @RequestMapping(path = "/ambiguous-produces", method = RequestMethod.GET, produces = "application/json") + @GetMapping(path = "/ambiguous-produces", produces = "application/json") public String ambigousProducesJson() { return "{}"; } - @CrossOrigin(origins = { "http://site1.com", "http://site2.com" }, allowedHeaders = { "header1", "header2" }, - exposedHeaders = { "header3", "header4" }, methods = RequestMethod.GET, maxAge = 123, allowCredentials = "false") + @CrossOrigin( + origins = { "http://site1.com", "http://site2.com" }, + allowedHeaders = { "header1", "header2" }, + exposedHeaders = { "header3", "header4" }, + methods = RequestMethod.GET, + maxAge = 123, + allowCredentials = "false") @RequestMapping(path = "/customized", method = { RequestMethod.GET, RequestMethod.POST }) public String customized() { return "customized"; } @CrossOrigin("http://site1.com") - @RequestMapping("/origin-value-attribute") + @GetMapping("/origin-value-attribute") public String customOriginDefinedViaValueAttribute() { return "value-attribute"; } @CrossOrigin("${myOrigin}") - @RequestMapping("/origin-placeholder") + @GetMapping("/origin-placeholder") public String customOriginDefinedViaPlaceholder() { return "placeholder"; } @@ -321,25 +300,25 @@ public class CrossOriginAnnotationIntegrationTests extends AbstractRequestMappin @RestController @CrossOrigin(allowCredentials = "false") + @SuppressWarnings("unused") private static class ClassLevelController { - @RequestMapping(path = "/foo", method = RequestMethod.GET) + @GetMapping("/foo") public String foo() { return "foo"; } @CrossOrigin - @RequestMapping(path = "/bar", method = RequestMethod.GET) + @GetMapping("/bar") public String bar() { return "bar"; } @CrossOrigin(allowCredentials = "true") - @RequestMapping(path = "/baz", method = RequestMethod.GET) + @GetMapping("/baz") public String baz() { return "baz"; } - } } diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/CorsConfigurationIntegrationTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/GlobalCorsConfigIntegrationTests.java similarity index 54% rename from spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/CorsConfigurationIntegrationTests.java rename to spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/GlobalCorsConfigIntegrationTests.java index b461559e2a..ce4dc61b6a 100644 --- a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/CorsConfigurationIntegrationTests.java +++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/GlobalCorsConfigIntegrationTests.java @@ -16,16 +16,14 @@ package org.springframework.web.reactive.result.method.annotation; -import static org.junit.Assert.*; +import org.junit.Before; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; -import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; @@ -36,34 +34,49 @@ import org.springframework.web.client.RestTemplate; import org.springframework.web.reactive.config.CorsRegistry; import org.springframework.web.reactive.config.WebReactiveConfiguration; -/** - * @author Sebastien Deleuze - */ -public class CorsConfigurationIntegrationTests extends AbstractRequestMappingIntegrationTests { +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.fail; + +/** + * + * Integration tests with {@code @RequestMapping} handler methods and global + * CORS configuration. + * + * @author Sebastien Deleuze + * @author Rossen Stoyanchev + */ +public class GlobalCorsConfigIntegrationTests extends AbstractRequestMappingIntegrationTests { + + private HttpHeaders headers; + + + @Before + public void setup() throws Exception { + super.setup(); + this.headers = new HttpHeaders(); + this.headers.setOrigin("http://localhost:9000"); + } - // JDK default HTTP client blacklist headers like Origin - private RestTemplate restTemplate = new RestTemplate(new HttpComponentsClientHttpRequestFactory()); @Override protected ApplicationContext initApplicationContext() { - AnnotationConfigApplicationContext wac = new AnnotationConfigApplicationContext(); - wac.register(WebConfig.class); - wac.refresh(); - return wac; + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + context.register(WebConfig.class); + context.refresh(); + return context; } @Override - RestTemplate getRestTemplate() { - return this.restTemplate; + protected RestTemplate initRestTemplate() { + // JDK default HTTP client blacklists headers like Origin + return new RestTemplate(new HttpComponentsClientHttpRequestFactory()); } + @Test public void actualRequestWithCorsEnabled() throws Exception { - HttpHeaders headers = new HttpHeaders(); - headers.add(HttpHeaders.ORIGIN, "http://localhost:9000"); - HttpEntity requestEntity = new HttpEntity(headers); - ResponseEntity entity = this.restTemplate.exchange(getUrl("/cors"), - HttpMethod.GET, requestEntity, String.class); + ResponseEntity entity = performGet("/cors", this.headers, String.class); assertEquals(HttpStatus.OK, entity.getStatusCode()); assertEquals("http://localhost:9000", entity.getHeaders().getAccessControlAllowOrigin()); assertEquals("cors", entity.getBody()); @@ -71,85 +84,58 @@ public class CorsConfigurationIntegrationTests extends AbstractRequestMappingInt @Test public void actualRequestWithCorsRejected() throws Exception { - HttpHeaders headers = new HttpHeaders(); - headers.add(HttpHeaders.ORIGIN, "http://localhost:9000"); - HttpEntity requestEntity = new HttpEntity(headers); try { - this.restTemplate.exchange(getUrl("/cors-restricted"), HttpMethod.GET, - requestEntity, String.class); + performGet("/cors-restricted", this.headers, String.class); + fail(); } catch (HttpClientErrorException e) { assertEquals(HttpStatus.FORBIDDEN, e.getStatusCode()); - return; } - fail(); } @Test public void actualRequestWithoutCorsEnabled() throws Exception { - HttpHeaders headers = new HttpHeaders(); - headers.add(HttpHeaders.ORIGIN, "http://localhost:9000"); - HttpEntity requestEntity = new HttpEntity(headers); - ResponseEntity entity = this.restTemplate.exchange(getUrl("/welcome"), - HttpMethod.GET, requestEntity, String.class); + ResponseEntity entity = performGet("/welcome", this.headers, String.class); assertEquals(HttpStatus.OK, entity.getStatusCode()); assertNull(entity.getHeaders().getAccessControlAllowOrigin()); assertEquals("welcome", entity.getBody()); } @Test - public void preflightRequestWithCorsEnabled() throws Exception { - HttpHeaders headers = new HttpHeaders(); - headers.add(HttpHeaders.ORIGIN, "http://localhost:9000"); - headers.add(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET"); - HttpEntity requestEntity = new HttpEntity(headers); - ResponseEntity entity = this.restTemplate.exchange(getUrl("/cors"), - HttpMethod.OPTIONS, requestEntity, String.class); + public void preFlightRequestWithCorsEnabled() throws Exception { + this.headers.add(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET"); + ResponseEntity entity = performOptions("/cors", this.headers, String.class); assertEquals(HttpStatus.OK, entity.getStatusCode()); assertEquals("http://localhost:9000", entity.getHeaders().getAccessControlAllowOrigin()); } @Test - public void preflightRequestWithCorsRejected() throws Exception { - HttpHeaders headers = new HttpHeaders(); - headers.add(HttpHeaders.ORIGIN, "http://localhost:9000"); - headers.add(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET"); - HttpEntity requestEntity = new HttpEntity(headers); + public void preFlightRequestWithCorsRejected() throws Exception { try { - this.restTemplate.exchange(getUrl("/cors-restricted"), HttpMethod.OPTIONS, - requestEntity, String.class); + this.headers.add(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET"); + performOptions("/cors-restricted", this.headers, String.class); + fail(); } catch (HttpClientErrorException e) { assertEquals(HttpStatus.FORBIDDEN, e.getStatusCode()); - return; } - fail(); } @Test - public void preflightRequestWithoutCorsEnabled() throws Exception { - HttpHeaders headers = new HttpHeaders(); - headers.add(HttpHeaders.ORIGIN, "http://localhost:9000"); - headers.add(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET"); - HttpEntity requestEntity = new HttpEntity(headers); + public void preFlightRequestWithoutCorsEnabled() throws Exception { try { - this.restTemplate.exchange(getUrl("/welcome"), HttpMethod.OPTIONS, - requestEntity, String.class); + this.headers.add(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET"); + performOptions("/welcome", this.headers, String.class); + fail(); } catch (HttpClientErrorException e) { assertEquals(HttpStatus.FORBIDDEN, e.getStatusCode()); - return; } - fail(); - } - - private String getUrl(String path) { - return "http://localhost:" + this.port + path; } @Configuration - @ComponentScan(resourcePattern = "**/CorsConfigurationIntegrationTests*.class") + @ComponentScan(resourcePattern = "**/GlobalCorsConfigIntegrationTests*.class") @SuppressWarnings({"unused", "WeakerAccess"}) static class WebConfig extends WebReactiveConfiguration { @@ -160,7 +146,7 @@ public class CorsConfigurationIntegrationTests extends AbstractRequestMappingInt } } - @RestController + @RestController @SuppressWarnings("unused") static class TestController { @GetMapping("/welcome") @@ -177,7 +163,6 @@ public class CorsConfigurationIntegrationTests extends AbstractRequestMappingInt public String corsRestricted() { return "corsRestricted"; } - } } diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingExceptionHandlingIntegrationTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingExceptionHandlingIntegrationTests.java index c11d2d6ded..04758fd8cc 100644 --- a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingExceptionHandlingIntegrationTests.java +++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingExceptionHandlingIntegrationTests.java @@ -24,6 +24,7 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpHeaders; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @@ -52,13 +53,13 @@ public class RequestMappingExceptionHandlingIntegrationTests extends AbstractReq @Test public void controllerThrowingException() throws Exception { String expected = "Recovered from error: Boo"; - assertEquals(expected, performGet("/thrown-exception", null, String.class).getBody()); + assertEquals(expected, performGet("/thrown-exception", new HttpHeaders(), String.class).getBody()); } @Test public void controllerReturnsMonoError() throws Exception { String expected = "Recovered from error: Boo"; - assertEquals(expected, performGet("/mono-error", null, String.class).getBody()); + assertEquals(expected, performGet("/mono-error", new HttpHeaders(), String.class).getBody()); } diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingIntegrationTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingIntegrationTests.java index 65d6ac4fb3..733a0da8df 100644 --- a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingIntegrationTests.java +++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingIntegrationTests.java @@ -24,6 +24,7 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; @@ -56,13 +57,13 @@ public class RequestMappingIntegrationTests extends AbstractRequestMappingIntegr @Test public void handleWithParam() throws Exception { String expected = "Hello George!"; - assertEquals(expected, performGet("/param?name=George", null, String.class).getBody()); + assertEquals(expected, performGet("/param?name=George", new HttpHeaders(), String.class).getBody()); } @Test public void longStreamResult() throws Exception { String[] expected = {"0", "1", "2", "3", "4"}; - assertArrayEquals(expected, performGet("/long-stream-result", null, String[].class).getBody()); + assertArrayEquals(expected, performGet("/long-stream-result", new HttpHeaders(), String[].class).getBody()); } @Test diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingMessageConversionIntegrationTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingMessageConversionIntegrationTests.java index 73b00b00fd..589f29fbb9 100644 --- a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingMessageConversionIntegrationTests.java +++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingMessageConversionIntegrationTests.java @@ -45,6 +45,7 @@ import org.springframework.core.io.Resource; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBufferFactory; import org.springframework.core.io.buffer.DefaultDataBufferFactory; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; @@ -94,25 +95,26 @@ public class RequestMappingMessageConversionIntegrationTests extends AbstractReq @Test public void byteBufferResponseBodyWithFlux() throws Exception { String expected = "Hello!"; - assertEquals(expected, performGet("/raw-response/flux", null, String.class).getBody()); + assertEquals(expected, performGet("/raw-response/flux", new HttpHeaders(), String.class).getBody()); } @Test public void byteBufferResponseBodyWithObservable() throws Exception { String expected = "Hello!"; - assertEquals(expected, performGet("/raw-response/observable", null, String.class).getBody()); + assertEquals(expected, performGet("/raw-response/observable", new HttpHeaders(), String.class).getBody()); } @Test public void byteBufferResponseBodyWithRxJava2Observable() throws Exception { String expected = "Hello!"; - assertEquals(expected, performGet("/raw-response/rxjava2-observable", null, String.class).getBody()); + assertEquals(expected, performGet("/raw-response/rxjava2-observable", + new HttpHeaders(), String.class).getBody()); } @Test public void byteBufferResponseBodyWithFlowable() throws Exception { String expected = "Hello!"; - assertEquals(expected, performGet("/raw-response/flowable", null, String.class).getBody()); + assertEquals(expected, performGet("/raw-response/flowable", new HttpHeaders(), String.class).getBody()); } @Test @@ -171,7 +173,7 @@ public class RequestMappingMessageConversionIntegrationTests extends AbstractReq @Test public void resource() throws Exception { - ResponseEntity response = performGet("/resource", null, byte[].class); + ResponseEntity response = performGet("/resource", new HttpHeaders(), byte[].class); assertEquals(HttpStatus.OK, response.getStatusCode()); assertTrue(response.hasBody()); diff --git a/spring-web/src/main/java/org/springframework/web/cors/reactive/DefaultCorsProcessor.java b/spring-web/src/main/java/org/springframework/web/cors/reactive/DefaultCorsProcessor.java index f2b7c57858..adfebe204a 100644 --- a/spring-web/src/main/java/org/springframework/web/cors/reactive/DefaultCorsProcessor.java +++ b/spring-web/src/main/java/org/springframework/web/cors/reactive/DefaultCorsProcessor.java @@ -63,12 +63,12 @@ public class DefaultCorsProcessor implements CorsProcessor { } if (responseHasCors(response)) { - logger.debug("Skip CORS processing: response already contains \"Access-Control-Allow-Origin\" header"); + logger.debug("Skip CORS: response already contains \"Access-Control-Allow-Origin\" header"); return true; } if (CorsUtils.isSameOrigin(request)) { - logger.debug("Skip CORS processing: request is from same origin"); + logger.debug("Skip CORS: request is from same origin"); return true; } diff --git a/spring-web/src/test/java/org/springframework/web/cors/reactive/DefaultCorsProcessorTests.java b/spring-web/src/test/java/org/springframework/web/cors/reactive/DefaultCorsProcessorTests.java index a2cb70958d..46cbaad09c 100644 --- a/spring-web/src/test/java/org/springframework/web/cors/reactive/DefaultCorsProcessorTests.java +++ b/spring-web/src/test/java/org/springframework/web/cors/reactive/DefaultCorsProcessorTests.java @@ -16,7 +16,6 @@ package org.springframework.web.cors.reactive; -import static org.junit.Assert.*; import org.junit.Before; import org.junit.Test; @@ -26,17 +25,22 @@ import org.springframework.http.HttpStatus; import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest; import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse; import org.springframework.web.cors.CorsConfiguration; -import org.springframework.web.cors.reactive.DefaultCorsProcessor; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.adapter.DefaultServerWebExchange; import org.springframework.web.server.session.MockWebSessionManager; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.springframework.http.HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS; +import static org.springframework.http.HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN; +import static org.springframework.http.HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS; +import static org.springframework.http.HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD; + /** - * Test reactive {@link DefaultCorsProcessor} with simple or preflight CORS request. + * {@link DefaultCorsProcessor} tests with simple or pre-flight CORS request. * * @author Sebastien Deleuze - * @author Rossen Stoyanchev - * @author Juergen Hoeller */ public class DefaultCorsProcessorTests { @@ -69,7 +73,7 @@ public class DefaultCorsProcessorTests { this.request.addHeader(HttpHeaders.ORIGIN, "http://domain2.com"); this.processor.processRequest(this.conf, this.exchange); - assertFalse(this.response.getHeaders().containsKey(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)); + assertFalse(this.response.getHeaders().containsKey(ACCESS_CONTROL_ALLOW_ORIGIN)); assertEquals(HttpStatus.FORBIDDEN, this.response.getStatusCode()); } @@ -79,7 +83,7 @@ public class DefaultCorsProcessorTests { this.request.addHeader(HttpHeaders.ORIGIN, "http://domain2.com"); this.processor.processRequest(null, this.exchange); - assertFalse(this.response.getHeaders().containsKey(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)); + assertFalse(this.response.getHeaders().containsKey(ACCESS_CONTROL_ALLOW_ORIGIN)); assertEquals(HttpStatus.OK, this.response.getStatusCode()); } @@ -90,8 +94,8 @@ public class DefaultCorsProcessorTests { this.conf.addAllowedOrigin("*"); this.processor.processRequest(this.conf, this.exchange); - assertTrue(this.response.getHeaders().containsKey(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)); - assertEquals("*", this.response.getHeaders().getFirst(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)); + assertTrue(this.response.getHeaders().containsKey(ACCESS_CONTROL_ALLOW_ORIGIN)); + assertEquals("*", this.response.getHeaders().getFirst(ACCESS_CONTROL_ALLOW_ORIGIN)); assertFalse(this.response.getHeaders().containsKey(HttpHeaders.ACCESS_CONTROL_MAX_AGE)); assertFalse(this.response.getHeaders().containsKey(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS)); assertEquals(HttpStatus.OK, this.response.getStatusCode()); @@ -107,8 +111,8 @@ public class DefaultCorsProcessorTests { this.conf.setAllowCredentials(true); this.processor.processRequest(this.conf, this.exchange); - assertTrue(this.response.getHeaders().containsKey(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)); - assertEquals("http://domain2.com", this.response.getHeaders().getFirst(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)); + assertTrue(this.response.getHeaders().containsKey(ACCESS_CONTROL_ALLOW_ORIGIN)); + assertEquals("http://domain2.com", this.response.getHeaders().getFirst(ACCESS_CONTROL_ALLOW_ORIGIN)); assertTrue(this.response.getHeaders().containsKey(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS)); assertEquals("true", this.response.getHeaders().getFirst(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS)); assertEquals(HttpStatus.OK, this.response.getStatusCode()); @@ -122,8 +126,8 @@ public class DefaultCorsProcessorTests { this.conf.setAllowCredentials(true); this.processor.processRequest(this.conf, this.exchange); - assertTrue(this.response.getHeaders().containsKey(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)); - assertEquals("http://domain2.com", this.response.getHeaders().getFirst(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)); + assertTrue(this.response.getHeaders().containsKey(ACCESS_CONTROL_ALLOW_ORIGIN)); + assertEquals("http://domain2.com", this.response.getHeaders().getFirst(ACCESS_CONTROL_ALLOW_ORIGIN)); assertTrue(this.response.getHeaders().containsKey(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS)); assertEquals("true", this.response.getHeaders().getFirst(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS)); assertEquals(HttpStatus.OK, this.response.getStatusCode()); @@ -136,7 +140,7 @@ public class DefaultCorsProcessorTests { this.conf.addAllowedOrigin("http://DOMAIN2.com"); this.processor.processRequest(this.conf, this.exchange); - assertTrue(this.response.getHeaders().containsKey(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)); + assertTrue(this.response.getHeaders().containsKey(ACCESS_CONTROL_ALLOW_ORIGIN)); assertEquals(HttpStatus.OK, this.response.getStatusCode()); } @@ -149,8 +153,8 @@ public class DefaultCorsProcessorTests { this.conf.addAllowedOrigin("http://domain2.com"); this.processor.processRequest(this.conf, this.exchange); - assertTrue(this.response.getHeaders().containsKey(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)); - assertEquals("http://domain2.com", this.response.getHeaders().getFirst(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)); + assertTrue(this.response.getHeaders().containsKey(ACCESS_CONTROL_ALLOW_ORIGIN)); + assertEquals("http://domain2.com", this.response.getHeaders().getFirst(ACCESS_CONTROL_ALLOW_ORIGIN)); assertTrue(this.response.getHeaders().containsKey(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS)); assertTrue(this.response.getHeaders().getFirst(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS).contains("header1")); assertTrue(this.response.getHeaders().getFirst(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS).contains("header2")); @@ -161,7 +165,7 @@ public class DefaultCorsProcessorTests { public void preflightRequestAllOriginsAllowed() throws Exception { this.request.setHttpMethod(HttpMethod.OPTIONS); this.request.addHeader(HttpHeaders.ORIGIN, "http://domain2.com"); - this.request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET"); + this.request.addHeader(ACCESS_CONTROL_REQUEST_METHOD, "GET"); this.conf.addAllowedOrigin("*"); this.processor.processRequest(this.conf, this.exchange); @@ -172,7 +176,7 @@ public class DefaultCorsProcessorTests { public void preflightRequestWrongAllowedMethod() throws Exception { this.request.setHttpMethod(HttpMethod.OPTIONS); this.request.addHeader(HttpHeaders.ORIGIN, "http://domain2.com"); - this.request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "DELETE"); + this.request.addHeader(ACCESS_CONTROL_REQUEST_METHOD, "DELETE"); this.conf.addAllowedOrigin("*"); this.processor.processRequest(this.conf, this.exchange); @@ -183,7 +187,7 @@ public class DefaultCorsProcessorTests { public void preflightRequestMatchedAllowedMethod() throws Exception { this.request.setHttpMethod(HttpMethod.OPTIONS); this.request.addHeader(HttpHeaders.ORIGIN, "http://domain2.com"); - this.request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET"); + this.request.addHeader(ACCESS_CONTROL_REQUEST_METHOD, "GET"); this.conf.addAllowedOrigin("*"); this.processor.processRequest(this.conf, this.exchange); @@ -197,7 +201,7 @@ public class DefaultCorsProcessorTests { this.request.addHeader(HttpHeaders.ORIGIN, "http://domain2.com"); this.processor.processRequest(this.conf, this.exchange); - assertFalse(this.response.getHeaders().containsKey(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)); + assertFalse(this.response.getHeaders().containsKey(ACCESS_CONTROL_ALLOW_ORIGIN)); assertEquals(HttpStatus.FORBIDDEN, this.response.getStatusCode()); } @@ -205,10 +209,10 @@ public class DefaultCorsProcessorTests { public void preflightRequestWithoutRequestMethod() throws Exception { this.request.setHttpMethod(HttpMethod.OPTIONS); this.request.addHeader(HttpHeaders.ORIGIN, "http://domain2.com"); - this.request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS, "Header1"); + this.request.addHeader(ACCESS_CONTROL_REQUEST_HEADERS, "Header1"); this.processor.processRequest(this.conf, this.exchange); - assertFalse(this.response.getHeaders().containsKey(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)); + assertFalse(this.response.getHeaders().containsKey(ACCESS_CONTROL_ALLOW_ORIGIN)); assertEquals(HttpStatus.FORBIDDEN, this.response.getStatusCode()); } @@ -216,11 +220,11 @@ public class DefaultCorsProcessorTests { public void preflightRequestWithRequestAndMethodHeaderButNoConfig() throws Exception { this.request.setHttpMethod(HttpMethod.OPTIONS); this.request.addHeader(HttpHeaders.ORIGIN, "http://domain2.com"); - this.request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET"); - this.request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS, "Header1"); + this.request.addHeader(ACCESS_CONTROL_REQUEST_METHOD, "GET"); + this.request.addHeader(ACCESS_CONTROL_REQUEST_HEADERS, "Header1"); this.processor.processRequest(this.conf, this.exchange); - assertFalse(this.response.getHeaders().containsKey(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)); + assertFalse(this.response.getHeaders().containsKey(ACCESS_CONTROL_ALLOW_ORIGIN)); assertEquals(HttpStatus.FORBIDDEN, this.response.getStatusCode()); } @@ -228,8 +232,8 @@ public class DefaultCorsProcessorTests { public void preflightRequestValidRequestAndConfig() throws Exception { this.request.setHttpMethod(HttpMethod.OPTIONS); this.request.addHeader(HttpHeaders.ORIGIN, "http://domain2.com"); - this.request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET"); - this.request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS, "Header1"); + this.request.addHeader(ACCESS_CONTROL_REQUEST_METHOD, "GET"); + this.request.addHeader(ACCESS_CONTROL_REQUEST_HEADERS, "Header1"); this.conf.addAllowedOrigin("*"); this.conf.addAllowedMethod("GET"); this.conf.addAllowedMethod("PUT"); @@ -237,8 +241,8 @@ public class DefaultCorsProcessorTests { this.conf.addAllowedHeader("header2"); this.processor.processRequest(this.conf, this.exchange); - assertTrue(this.response.getHeaders().containsKey(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)); - assertEquals("*", this.response.getHeaders().getFirst(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)); + assertTrue(this.response.getHeaders().containsKey(ACCESS_CONTROL_ALLOW_ORIGIN)); + assertEquals("*", this.response.getHeaders().getFirst(ACCESS_CONTROL_ALLOW_ORIGIN)); assertTrue(this.response.getHeaders().containsKey(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS)); assertEquals("GET,PUT", this.response.getHeaders().getFirst(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS)); assertFalse(this.response.getHeaders().containsKey(HttpHeaders.ACCESS_CONTROL_MAX_AGE)); @@ -249,8 +253,8 @@ public class DefaultCorsProcessorTests { public void preflightRequestCredentials() throws Exception { this.request.setHttpMethod(HttpMethod.OPTIONS); this.request.addHeader(HttpHeaders.ORIGIN, "http://domain2.com"); - this.request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET"); - this.request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS, "Header1"); + this.request.addHeader(ACCESS_CONTROL_REQUEST_METHOD, "GET"); + this.request.addHeader(ACCESS_CONTROL_REQUEST_HEADERS, "Header1"); this.conf.addAllowedOrigin("http://domain1.com"); this.conf.addAllowedOrigin("http://domain2.com"); this.conf.addAllowedOrigin("http://domain3.com"); @@ -258,8 +262,8 @@ public class DefaultCorsProcessorTests { this.conf.setAllowCredentials(true); this.processor.processRequest(this.conf, this.exchange); - assertTrue(this.response.getHeaders().containsKey(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)); - assertEquals("http://domain2.com", this.response.getHeaders().getFirst(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)); + assertTrue(this.response.getHeaders().containsKey(ACCESS_CONTROL_ALLOW_ORIGIN)); + assertEquals("http://domain2.com", this.response.getHeaders().getFirst(ACCESS_CONTROL_ALLOW_ORIGIN)); assertTrue(this.response.getHeaders().containsKey(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS)); assertEquals("true", this.response.getHeaders().getFirst(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS)); assertEquals(HttpStatus.OK, this.response.getStatusCode()); @@ -269,8 +273,8 @@ public class DefaultCorsProcessorTests { public void preflightRequestCredentialsWithOriginWildcard() throws Exception { this.request.setHttpMethod(HttpMethod.OPTIONS); this.request.addHeader(HttpHeaders.ORIGIN, "http://domain2.com"); - this.request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET"); - this.request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS, "Header1"); + this.request.addHeader(ACCESS_CONTROL_REQUEST_METHOD, "GET"); + this.request.addHeader(ACCESS_CONTROL_REQUEST_HEADERS, "Header1"); this.conf.addAllowedOrigin("http://domain1.com"); this.conf.addAllowedOrigin("*"); this.conf.addAllowedOrigin("http://domain3.com"); @@ -278,8 +282,8 @@ public class DefaultCorsProcessorTests { this.conf.setAllowCredentials(true); this.processor.processRequest(this.conf, this.exchange); - assertTrue(this.response.getHeaders().containsKey(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)); - assertEquals("http://domain2.com", this.response.getHeaders().getFirst(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)); + assertTrue(this.response.getHeaders().containsKey(ACCESS_CONTROL_ALLOW_ORIGIN)); + assertEquals("http://domain2.com", this.response.getHeaders().getFirst(ACCESS_CONTROL_ALLOW_ORIGIN)); assertEquals(HttpStatus.OK, this.response.getStatusCode()); } @@ -287,19 +291,19 @@ public class DefaultCorsProcessorTests { public void preflightRequestAllowedHeaders() throws Exception { this.request.setHttpMethod(HttpMethod.OPTIONS); this.request.addHeader(HttpHeaders.ORIGIN, "http://domain2.com"); - this.request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET"); - this.request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS, "Header1, Header2"); + this.request.addHeader(ACCESS_CONTROL_REQUEST_METHOD, "GET"); + this.request.addHeader(ACCESS_CONTROL_REQUEST_HEADERS, "Header1, Header2"); this.conf.addAllowedHeader("Header1"); this.conf.addAllowedHeader("Header2"); this.conf.addAllowedHeader("Header3"); this.conf.addAllowedOrigin("http://domain2.com"); this.processor.processRequest(this.conf, this.exchange); - assertTrue(this.response.getHeaders().containsKey(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)); - assertTrue(this.response.getHeaders().containsKey(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS)); - assertTrue(this.response.getHeaders().getFirst(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS).contains("Header1")); - assertTrue(this.response.getHeaders().getFirst(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS).contains("Header2")); - assertFalse(this.response.getHeaders().getFirst(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS).contains("Header3")); + assertTrue(this.response.getHeaders().containsKey(ACCESS_CONTROL_ALLOW_ORIGIN)); + assertTrue(this.response.getHeaders().containsKey(ACCESS_CONTROL_ALLOW_HEADERS)); + assertTrue(this.response.getHeaders().getFirst(ACCESS_CONTROL_ALLOW_HEADERS).contains("Header1")); + assertTrue(this.response.getHeaders().getFirst(ACCESS_CONTROL_ALLOW_HEADERS).contains("Header2")); + assertFalse(this.response.getHeaders().getFirst(ACCESS_CONTROL_ALLOW_HEADERS).contains("Header3")); assertEquals(HttpStatus.OK, this.response.getStatusCode()); } @@ -307,17 +311,17 @@ public class DefaultCorsProcessorTests { public void preflightRequestAllowsAllHeaders() throws Exception { this.request.setHttpMethod(HttpMethod.OPTIONS); this.request.addHeader(HttpHeaders.ORIGIN, "http://domain2.com"); - this.request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET"); - this.request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS, "Header1, Header2"); + this.request.addHeader(ACCESS_CONTROL_REQUEST_METHOD, "GET"); + this.request.addHeader(ACCESS_CONTROL_REQUEST_HEADERS, "Header1, Header2"); this.conf.addAllowedHeader("*"); this.conf.addAllowedOrigin("http://domain2.com"); this.processor.processRequest(this.conf, this.exchange); - assertTrue(this.response.getHeaders().containsKey(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)); - assertTrue(this.response.getHeaders().containsKey(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS)); - assertTrue(this.response.getHeaders().getFirst(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS).contains("Header1")); - assertTrue(this.response.getHeaders().getFirst(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS).contains("Header2")); - assertFalse(this.response.getHeaders().getFirst(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS).contains("*")); + assertTrue(this.response.getHeaders().containsKey(ACCESS_CONTROL_ALLOW_ORIGIN)); + assertTrue(this.response.getHeaders().containsKey(ACCESS_CONTROL_ALLOW_HEADERS)); + assertTrue(this.response.getHeaders().getFirst(ACCESS_CONTROL_ALLOW_HEADERS).contains("Header1")); + assertTrue(this.response.getHeaders().getFirst(ACCESS_CONTROL_ALLOW_HEADERS).contains("Header2")); + assertFalse(this.response.getHeaders().getFirst(ACCESS_CONTROL_ALLOW_HEADERS).contains("*")); assertEquals(HttpStatus.OK, this.response.getStatusCode()); } @@ -325,14 +329,14 @@ public class DefaultCorsProcessorTests { public void preflightRequestWithEmptyHeaders() throws Exception { this.request.setHttpMethod(HttpMethod.OPTIONS); this.request.addHeader(HttpHeaders.ORIGIN, "http://domain2.com"); - this.request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET"); - this.request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS, ""); + this.request.addHeader(ACCESS_CONTROL_REQUEST_METHOD, "GET"); + this.request.addHeader(ACCESS_CONTROL_REQUEST_HEADERS, ""); this.conf.addAllowedHeader("*"); this.conf.addAllowedOrigin("http://domain2.com"); this.processor.processRequest(this.conf, this.exchange); - assertTrue(this.response.getHeaders().containsKey(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)); - assertFalse(this.response.getHeaders().containsKey(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS)); + assertTrue(this.response.getHeaders().containsKey(ACCESS_CONTROL_ALLOW_ORIGIN)); + assertFalse(this.response.getHeaders().containsKey(ACCESS_CONTROL_ALLOW_HEADERS)); assertEquals(HttpStatus.OK, this.response.getStatusCode()); } @@ -340,11 +344,11 @@ public class DefaultCorsProcessorTests { public void preflightRequestWithNullConfig() throws Exception { this.request.setHttpMethod(HttpMethod.OPTIONS); this.request.addHeader(HttpHeaders.ORIGIN, "http://domain2.com"); - this.request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET"); + this.request.addHeader(ACCESS_CONTROL_REQUEST_METHOD, "GET"); this.conf.addAllowedOrigin("*"); this.processor.processRequest(null, this.exchange); - assertFalse(this.response.getHeaders().containsKey(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)); + assertFalse(this.response.getHeaders().containsKey(ACCESS_CONTROL_ALLOW_ORIGIN)); assertEquals(HttpStatus.FORBIDDEN, this.response.getStatusCode()); } diff --git a/spring-web/src/test/java/org/springframework/web/cors/reactive/UrlBasedCorsConfigurationSourceTests.java b/spring-web/src/test/java/org/springframework/web/cors/reactive/UrlBasedCorsConfigurationSourceTests.java index f0be0bfade..9ad06c2b8c 100644 --- a/spring-web/src/test/java/org/springframework/web/cors/reactive/UrlBasedCorsConfigurationSourceTests.java +++ b/spring-web/src/test/java/org/springframework/web/cors/reactive/UrlBasedCorsConfigurationSourceTests.java @@ -16,8 +16,6 @@ package org.springframework.web.cors.reactive; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; import org.junit.Test; import org.springframework.http.HttpMethod; @@ -29,19 +27,23 @@ import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.adapter.DefaultServerWebExchange; import org.springframework.web.server.session.MockWebSessionManager; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + /** - * Unit tests for reactive {@link UrlBasedCorsConfigurationSource}. + * Unit tests for {@link UrlBasedCorsConfigurationSource}. + * * @author Sebastien Deleuze + * @author Rossen Stoyanchev */ public class UrlBasedCorsConfigurationSourceTests { private final UrlBasedCorsConfigurationSource configSource = new UrlBasedCorsConfigurationSource(); + @Test public void empty() { - ServerHttpRequest request = new MockServerHttpRequest(HttpMethod.GET, "/bar/test.html"); - ServerWebExchange exchange = new DefaultServerWebExchange(request, - new MockServerHttpResponse(), new MockWebSessionManager()); + ServerWebExchange exchange = createExchange(HttpMethod.GET, "/bar/test.html"); assertNull(this.configSource.getCorsConfiguration(exchange)); } @@ -49,15 +51,12 @@ public class UrlBasedCorsConfigurationSourceTests { public void registerAndMatch() { CorsConfiguration config = new CorsConfiguration(); this.configSource.registerCorsConfiguration("/bar/**", config); - assertNull(this.configSource.getCorsConfiguration( - new DefaultServerWebExchange( - new MockServerHttpRequest(HttpMethod.GET, "/foo/test.html"), - new MockServerHttpResponse(), - new MockWebSessionManager()))); - assertEquals(config, this.configSource.getCorsConfiguration(new DefaultServerWebExchange( - new MockServerHttpRequest(HttpMethod.GET, "/bar/test.html"), - new MockServerHttpResponse(), - new MockWebSessionManager()))); + + ServerWebExchange exchange = createExchange(HttpMethod.GET, "/foo/test.html"); + assertNull(this.configSource.getCorsConfiguration(exchange)); + + exchange = createExchange(HttpMethod.GET, "/bar/test.html"); + assertEquals(config, this.configSource.getCorsConfiguration(exchange)); } @Test(expected = UnsupportedOperationException.class) @@ -65,4 +64,12 @@ public class UrlBasedCorsConfigurationSourceTests { this.configSource.getCorsConfigurations().put("/**", new CorsConfiguration()); } + + private ServerWebExchange createExchange(HttpMethod httpMethod, String url) { + ServerHttpRequest request = new MockServerHttpRequest(httpMethod, url); + MockServerHttpResponse response = new MockServerHttpResponse(); + MockWebSessionManager sessionManager = new MockWebSessionManager(); + return new DefaultServerWebExchange(request, response, sessionManager); + } + } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/CorsRegistration.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/CorsRegistration.java index 41a3ae56bc..568dd9bfdf 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/CorsRegistration.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/CorsRegistration.java @@ -46,6 +46,7 @@ public class CorsRegistration { private final CorsConfiguration config; + public CorsRegistration(String pathPattern) { this.pathPattern = pathPattern; // Same implicit default values as the @CrossOrigin annotation + allows simple methods diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMapping.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMapping.java index 39c7a24407..a44ee50143 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMapping.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMapping.java @@ -78,9 +78,9 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport private final List adaptedInterceptors = new ArrayList<>(); - private CorsProcessor corsProcessor = new DefaultCorsProcessor(); + private final UrlBasedCorsConfigurationSource globalCorsConfigSource = new UrlBasedCorsConfigurationSource(); - private final UrlBasedCorsConfigurationSource corsConfigSource = new UrlBasedCorsConfigurationSource(); + private CorsProcessor corsProcessor = new DefaultCorsProcessor(); /** @@ -123,7 +123,7 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport */ public void setAlwaysUseFullPath(boolean alwaysUseFullPath) { this.urlPathHelper.setAlwaysUseFullPath(alwaysUseFullPath); - this.corsConfigSource.setAlwaysUseFullPath(alwaysUseFullPath); + this.globalCorsConfigSource.setAlwaysUseFullPath(alwaysUseFullPath); } /** @@ -135,7 +135,7 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport */ public void setUrlDecode(boolean urlDecode) { this.urlPathHelper.setUrlDecode(urlDecode); - this.corsConfigSource.setUrlDecode(urlDecode); + this.globalCorsConfigSource.setUrlDecode(urlDecode); } /** @@ -145,7 +145,7 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport */ public void setRemoveSemicolonContent(boolean removeSemicolonContent) { this.urlPathHelper.setRemoveSemicolonContent(removeSemicolonContent); - this.corsConfigSource.setRemoveSemicolonContent(removeSemicolonContent); + this.globalCorsConfigSource.setRemoveSemicolonContent(removeSemicolonContent); } /** @@ -157,7 +157,7 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport public void setUrlPathHelper(UrlPathHelper urlPathHelper) { Assert.notNull(urlPathHelper, "UrlPathHelper must not be null"); this.urlPathHelper = urlPathHelper; - this.corsConfigSource.setUrlPathHelper(urlPathHelper); + this.globalCorsConfigSource.setUrlPathHelper(urlPathHelper); } /** @@ -175,7 +175,7 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport public void setPathMatcher(PathMatcher pathMatcher) { Assert.notNull(pathMatcher, "PathMatcher must not be null"); this.pathMatcher = pathMatcher; - this.corsConfigSource.setPathMatcher(pathMatcher); + this.globalCorsConfigSource.setPathMatcher(pathMatcher); } /** @@ -200,6 +200,23 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport this.interceptors.addAll(Arrays.asList(interceptors)); } + /** + * Set "global" CORS configuration based on URL patterns. By default the first + * matching URL pattern is combined with the CORS configuration for the + * handler, if any. + * @since 4.2 + */ + public void setCorsConfigurations(Map corsConfigurations) { + this.globalCorsConfigSource.setCorsConfigurations(corsConfigurations); + } + + /** + * Get the "global" CORS configuration. + */ + public Map getCorsConfigurations() { + return this.globalCorsConfigSource.getCorsConfigurations(); + } + /** * Configure a custom {@link CorsProcessor} to use to apply the matched * {@link CorsConfiguration} for a request. @@ -218,23 +235,6 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport return this.corsProcessor; } - /** - * Set "global" CORS configuration based on URL patterns. By default the first - * matching URL pattern is combined with the CORS configuration for the - * handler, if any. - * @since 4.2 - */ - public void setCorsConfigurations(Map corsConfigurations) { - this.corsConfigSource.setCorsConfigurations(corsConfigurations); - } - - /** - * Get the CORS configuration. - */ - public Map getCorsConfigurations() { - return this.corsConfigSource.getCorsConfigurations(); - } - /** * Initializes the interceptors. @@ -364,7 +364,7 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request); if (CorsUtils.isCorsRequest(request)) { - CorsConfiguration globalConfig = this.corsConfigSource.getCorsConfiguration(request); + CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request); CorsConfiguration handlerConfig = getCorsConfiguration(handler, request); CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig); executionChain = getCorsHandlerExecutionChain(request, executionChain, config); diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java index b4abdc1698..ce740375c4 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java @@ -293,7 +293,8 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi @Override protected CorsConfiguration initCorsConfiguration(Object handler, Method method, RequestMappingInfo mappingInfo) { HandlerMethod handlerMethod = createHandlerMethod(handler, method); - CrossOrigin typeAnnotation = AnnotatedElementUtils.findMergedAnnotation(handlerMethod.getBeanType(), CrossOrigin.class); + Class beanType = handlerMethod.getBeanType(); + CrossOrigin typeAnnotation = AnnotatedElementUtils.findMergedAnnotation(beanType, CrossOrigin.class); CrossOrigin methodAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, CrossOrigin.class); if (typeAnnotation == null && methodAnnotation == null) {