Add an option to disable automatic addition of CORS header
Issues: SPR-12283
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -64,6 +64,8 @@ public class SockJsServiceRegistration {
|
||||
|
||||
private final List<String> allowedOrigins = new ArrayList<String>();
|
||||
|
||||
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.
|
||||
* <p>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.
|
||||
* <p>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);
|
||||
}
|
||||
|
||||
@@ -88,6 +88,8 @@ public abstract class AbstractSockJsService implements SockJsService {
|
||||
|
||||
private final List<String> allowedOrigins = new ArrayList<String>(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.
|
||||
* <p>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;
|
||||
|
||||
@@ -232,6 +232,14 @@
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="suppress-cors" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation source="java:org.springframework.web.socket.sockjs.support.AbstractSockJsService"><![CDATA[
|
||||
This option can be used to disable automatic addition of CORS headers for SockJS requests.
|
||||
The default value is "false".
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="stomp-broker-relay">
|
||||
|
||||
@@ -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<TransportType, TransportHandler> transportHandlers = defaultSockJsService.getTransportHandlers();
|
||||
assertThat(transportHandlers.values(),
|
||||
@@ -232,6 +233,7 @@ public class HandlersBeanDefinitionParserTests {
|
||||
List<HandshakeInterceptor> 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) {
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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<HttpRequestHandler, String> 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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<websocket:mapping path="/test" handler="testHandler"/>
|
||||
<websocket:sockjs name="testSockJsService" scheduler="testTaskScheduler" websocket-enabled="false"
|
||||
session-cookie-needed="false" stream-bytes-limit="2048" disconnect-delay="256"
|
||||
message-cache-size="1024" heartbeat-time="20" message-codec="messageCodec">
|
||||
message-cache-size="1024" heartbeat-time="20" message-codec="messageCodec" suppress-cors="true">
|
||||
<websocket:transport-handlers register-defaults="false">
|
||||
<bean class="org.springframework.web.socket.sockjs.transport.handler.XhrPollingTransportHandler"/>
|
||||
<ref bean="xhrStreamingTransportHandler"/>
|
||||
|
||||
@@ -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).
|
||||
|
||||
Reference in New Issue
Block a user