diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/config/WebSocketNamespaceUtils.java b/spring-websocket/src/main/java/org/springframework/web/socket/config/WebSocketNamespaceUtils.java index 3689164485..d44d61e049 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/config/WebSocketNamespaceUtils.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/config/WebSocketNamespaceUtils.java @@ -145,6 +145,10 @@ class WebSocketNamespaceUtils { if (!attrValue.isEmpty()) { sockJsServiceDef.getPropertyValues().add("messageCodec", new RuntimeBeanReference(attrValue)); } + attrValue = sockJsElement.getAttribute("suppress-cors"); + if (!attrValue.isEmpty()) { + sockJsServiceDef.getPropertyValues().add("suppressCors", Boolean.valueOf(attrValue)); + } sockJsServiceDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); String sockJsServiceName = context.getReaderContext().registerWithGeneratedName(sockJsServiceDef); return new RuntimeBeanReference(sockJsServiceName); diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/SockJsServiceRegistration.java b/spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/SockJsServiceRegistration.java index 0b7b028c68..901db2235c 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/SockJsServiceRegistration.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/SockJsServiceRegistration.java @@ -64,6 +64,8 @@ public class SockJsServiceRegistration { private final List allowedOrigins = new ArrayList(); + private Boolean suppressCors; + private SockJsMessageCodec messageCodec; @@ -204,6 +206,17 @@ public class SockJsServiceRegistration { return this; } + /** + * This option can be used to disable automatic addition of CORS headers for + * SockJS requests. + *

The default value is "false". + * @since 4.1.2 + */ + public SockJsServiceRegistration setSupressCors(boolean suppressCors) { + this.suppressCors = suppressCors; + return this; + } + /** * The codec to use for encoding and decoding SockJS messages. *

By default {@code Jackson2SockJsMessageCodec} is used requiring the @@ -251,6 +264,9 @@ public class SockJsServiceRegistration { if (this.webSocketEnabled != null) { service.setWebSocketEnabled(this.webSocketEnabled); } + if (this.suppressCors != null) { + service.setSuppressCors(this.suppressCors); + } if (!this.allowedOrigins.isEmpty()) { service.setAllowedOrigins(this.allowedOrigins); } diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/AbstractSockJsService.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/AbstractSockJsService.java index 6b006bdf1d..34bbb39b9a 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/AbstractSockJsService.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/AbstractSockJsService.java @@ -88,6 +88,8 @@ public abstract class AbstractSockJsService implements SockJsService { private final List allowedOrigins = new ArrayList(Arrays.asList("*")); + private boolean suppressCors = false; + public AbstractSockJsService(TaskScheduler scheduler) { Assert.notNull(scheduler, "TaskScheduler must not be null"); @@ -293,6 +295,24 @@ public abstract class AbstractSockJsService implements SockJsService { return Collections.unmodifiableList(allowedOrigins); } + /** + * This option can be used to disable automatic addition of CORS headers for + * SockJS requests. + *

The default value is "false". + * @since 4.1.2 + */ + public void setSuppressCors(boolean suppressCors) { + this.suppressCors = suppressCors; + } + + /** + * @since 4.1.2 + * @see #setSuppressCors(boolean) + */ + public boolean shouldSuppressCors() { + return this.suppressCors; + } + /** * This method determines the SockJS path and handles SockJS static URLs. * Session URLs and raw WebSocket requests are delegated to abstract methods. @@ -426,7 +446,7 @@ public abstract class AbstractSockJsService implements SockJsService { // See SPR-11919 and https://issues.jboss.org/browse/WFLY-3474 } - if(origin != null && !hasCorsResponseHeaders) { + if(!this.suppressCors && origin != null && !hasCorsResponseHeaders) { addCorsHeaders(request, response, httpMethods); } return true; diff --git a/spring-websocket/src/main/resources/org/springframework/web/socket/config/spring-websocket-4.1.xsd b/spring-websocket/src/main/resources/org/springframework/web/socket/config/spring-websocket-4.1.xsd index e58631e599..740c22b424 100644 --- a/spring-websocket/src/main/resources/org/springframework/web/socket/config/spring-websocket-4.1.xsd +++ b/spring-websocket/src/main/resources/org/springframework/web/socket/config/spring-websocket-4.1.xsd @@ -232,6 +232,14 @@ ]]> + + + + + diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/config/HandlersBeanDefinitionParserTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/config/HandlersBeanDefinitionParserTests.java index 3f79367f83..97c0e1326d 100644 --- a/spring-websocket/src/test/java/org/springframework/web/socket/config/HandlersBeanDefinitionParserTests.java +++ b/spring-websocket/src/test/java/org/springframework/web/socket/config/HandlersBeanDefinitionParserTests.java @@ -178,6 +178,7 @@ public class HandlersBeanDefinitionParserTests { assertThat(sockJsService, instanceOf(DefaultSockJsService.class)); DefaultSockJsService defaultSockJsService = (DefaultSockJsService) sockJsService; assertThat(defaultSockJsService.getTaskScheduler(), instanceOf(ThreadPoolTaskScheduler.class)); + assertFalse(defaultSockJsService.shouldSuppressCors()); Map transportHandlers = defaultSockJsService.getTransportHandlers(); assertThat(transportHandlers.values(), @@ -232,6 +233,7 @@ public class HandlersBeanDefinitionParserTests { List interceptors = transportService.getHandshakeInterceptors(); assertThat(interceptors, contains(instanceOf(OriginHandshakeInterceptor.class))); assertEquals(Arrays.asList("http://mydomain1.com", "http://mydomain2.com"), transportService.getAllowedOrigins()); + assertTrue(transportService.shouldSuppressCors()); } private void loadBeanDefinitions(String fileName) { diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/config/MessageBrokerBeanDefinitionParserTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/config/MessageBrokerBeanDefinitionParserTests.java index ab9fcb6a76..192aaa53a5 100644 --- a/spring-websocket/src/test/java/org/springframework/web/socket/config/MessageBrokerBeanDefinitionParserTests.java +++ b/spring-websocket/src/test/java/org/springframework/web/socket/config/MessageBrokerBeanDefinitionParserTests.java @@ -154,6 +154,7 @@ public class MessageBrokerBeanDefinitionParserTests { .getTransportHandlers().get(TransportType.WEBSOCKET); assertNotNull(wsTransportHandler.getHandshakeHandler()); assertThat(wsTransportHandler.getHandshakeHandler(), Matchers.instanceOf(TestHandshakeHandler.class)); + assertFalse(defaultSockJsService.shouldSuppressCors()); ThreadPoolTaskScheduler scheduler = (ThreadPoolTaskScheduler) defaultSockJsService.getTaskScheduler(); assertEquals(Runtime.getRuntime().availableProcessors(), scheduler.getScheduledThreadPoolExecutor().getCorePoolSize()); diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/config/annotation/WebMvcStompWebSocketEndpointRegistrationTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/config/annotation/WebMvcStompWebSocketEndpointRegistrationTests.java index 048050150a..de6e8888d3 100644 --- a/spring-websocket/src/test/java/org/springframework/web/socket/config/annotation/WebMvcStompWebSocketEndpointRegistrationTests.java +++ b/spring-websocket/src/test/java/org/springframework/web/socket/config/annotation/WebMvcStompWebSocketEndpointRegistrationTests.java @@ -104,6 +104,7 @@ public class WebMvcStompWebSocketEndpointRegistrationTests { assertNotNull(requestHandler.getSockJsService()); DefaultSockJsService sockJsService = (DefaultSockJsService)requestHandler.getSockJsService(); assertEquals(Arrays.asList(origin), sockJsService.getAllowedOrigins()); + assertFalse(sockJsService.shouldSuppressCors()); registration = new WebMvcStompWebSocketEndpointRegistration(new String[] {"/foo"}, this.handler, this.scheduler); @@ -114,6 +115,22 @@ public class WebMvcStompWebSocketEndpointRegistrationTests { assertNotNull(requestHandler.getSockJsService()); sockJsService = (DefaultSockJsService)requestHandler.getSockJsService(); assertEquals(Arrays.asList(origin), sockJsService.getAllowedOrigins()); + assertFalse(sockJsService.shouldSuppressCors()); + } + + @Test // SPR-12283 + public void disableCorsWithSockJsService() { + WebMvcStompWebSocketEndpointRegistration registration = + new WebMvcStompWebSocketEndpointRegistration(new String[] {"/foo"}, this.handler, this.scheduler); + + registration.withSockJS().setSupressCors(true); + + MultiValueMap mappings = registration.getMappings(); + assertEquals(1, mappings.size()); + SockJsHttpRequestHandler requestHandler = (SockJsHttpRequestHandler)mappings.entrySet().iterator().next().getKey(); + assertNotNull(requestHandler.getSockJsService()); + DefaultSockJsService sockJsService = (DefaultSockJsService)requestHandler.getSockJsService(); + assertTrue(sockJsService.shouldSuppressCors()); } @Test diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/support/SockJsServiceTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/support/SockJsServiceTests.java index ebad39d5ae..d9d5735f6f 100644 --- a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/support/SockJsServiceTests.java +++ b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/support/SockJsServiceTests.java @@ -237,6 +237,42 @@ public class SockJsServiceTests extends AbstractHttpRequestTests { assertEquals("Origin", this.servletResponse.getHeader("Vary")); } + @Test // SPR-12283 + public void handleInfoOptionsWithOriginAndCorsDisabled() throws Exception { + setOrigin("http://mydomain2.com"); + this.service.setSuppressCors(true); + + this.servletRequest.addHeader("Access-Control-Request-Headers", "Last-Modified"); + resetResponseAndHandleRequest("OPTIONS", "/echo/info", HttpStatus.NO_CONTENT); + this.response.flush(); + assertNull(this.servletResponse.getHeader("Access-Control-Allow-Origin")); + assertNull(this.servletResponse.getHeader("Access-Control-Allow-Credentials")); + assertNull(this.servletResponse.getHeader("Access-Control-Allow-Headers")); + assertNull(this.servletResponse.getHeader("Access-Control-Allow-Methods")); + assertNull(this.servletResponse.getHeader("Access-Control-Max-Age")); + assertEquals("Origin", this.servletResponse.getHeader("Vary")); + + this.service.setAllowedOrigins(Arrays.asList("http://mydomain1.com")); + resetResponseAndHandleRequest("OPTIONS", "/echo/info", HttpStatus.FORBIDDEN); + this.response.flush(); + assertNull(this.servletResponse.getHeader("Access-Control-Allow-Origin")); + assertNull(this.servletResponse.getHeader("Access-Control-Allow-Credentials")); + assertNull(this.servletResponse.getHeader("Access-Control-Allow-Headers")); + assertNull(this.servletResponse.getHeader("Access-Control-Allow-Methods")); + assertNull(this.servletResponse.getHeader("Access-Control-Max-Age")); + assertNull(this.servletResponse.getHeader("Vary")); + + this.service.setAllowedOrigins(Arrays.asList("http://mydomain1.com", "http://mydomain2.com", "http://mydomain3.com")); + resetResponseAndHandleRequest("OPTIONS", "/echo/info", HttpStatus.NO_CONTENT); + this.response.flush(); + assertNull(this.servletResponse.getHeader("Access-Control-Allow-Origin")); + assertNull(this.servletResponse.getHeader("Access-Control-Allow-Credentials")); + assertNull(this.servletResponse.getHeader("Access-Control-Allow-Headers")); + assertNull(this.servletResponse.getHeader("Access-Control-Allow-Methods")); + assertNull(this.servletResponse.getHeader("Access-Control-Max-Age")); + assertEquals("Origin", this.servletResponse.getHeader("Vary")); + } + @Test public void handleIframeRequest() throws Exception { resetResponseAndHandleRequest("GET", "/echo/iframe.html", HttpStatus.OK); diff --git a/spring-websocket/src/test/resources/org/springframework/web/socket/config/websocket-config-handlers-sockjs-attributes.xml b/spring-websocket/src/test/resources/org/springframework/web/socket/config/websocket-config-handlers-sockjs-attributes.xml index e43513a579..f319e7be0d 100644 --- a/spring-websocket/src/test/resources/org/springframework/web/socket/config/websocket-config-handlers-sockjs-attributes.xml +++ b/spring-websocket/src/test/resources/org/springframework/web/socket/config/websocket-config-handlers-sockjs-attributes.xml @@ -9,7 +9,7 @@ + message-cache-size="1024" heartbeat-time="20" message-codec="messageCodec" suppress-cors="true"> diff --git a/src/asciidoc/index.adoc b/src/asciidoc/index.adoc index 5d2e85b1e4..cbf252b655 100644 --- a/src/asciidoc/index.adoc +++ b/src/asciidoc/index.adoc @@ -39606,9 +39606,12 @@ presence of CORS headers in the response is detected. So if an application is already configured to provide CORS support, e.g. through a Servlet Filter, Spring's SockJsService will skip this part. +It is also possible to disable the addition of these CORS headers thanks to the +`suppressCors` property in Spring's SockJsService. + The following is the list of headers and values expected by SockJS: -* `"Access-Control-Allow-Origin"` - intitialized from the value of the "origin" request header or "*". +* `"Access-Control-Allow-Origin"` - initialized from the value of the "Origin" request header. * `"Access-Control-Allow-Credentials"` - always set to `true`. * `"Access-Control-Request-Headers"` - initialized from values from the equivalent request header. * `"Access-Control-Allow-Methods"` - the HTTP methods a transport supports (see `TransportType` enum).