Change SockJS and Websocket default allowedOrigins to same origin
This commit adds support for a same origin check that compares
Origin header to Host header. It also changes the default setting
from all origins allowed to only same origin allowed.
Issues: SPR-12697, SPR-12685
(cherry picked from commit 6062e15)
This commit is contained in:
committed by
Juergen Hoeller
parent
cc78d40c6b
commit
23fa37b08b
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2014 the original author or authors.
|
||||
* Copyright 2002-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -83,11 +83,7 @@ class HandlersBeanDefinitionParser implements BeanDefinitionParser {
|
||||
ManagedList<? super Object> interceptors = WebSocketNamespaceUtils.parseBeanSubElements(interceptorsElement, context);
|
||||
String allowedOriginsAttribute = element.getAttribute("allowed-origins");
|
||||
List<String> allowedOrigins = Arrays.asList(StringUtils.tokenizeToStringArray(allowedOriginsAttribute, ","));
|
||||
if (!allowedOrigins.isEmpty()) {
|
||||
OriginHandshakeInterceptor interceptor = new OriginHandshakeInterceptor();
|
||||
interceptor.setAllowedOrigins(allowedOrigins);
|
||||
interceptors.add(interceptor);
|
||||
}
|
||||
interceptors.add(new OriginHandshakeInterceptor(allowedOrigins));
|
||||
strategy = new WebSocketHandlerMappingStrategy(handshakeHandler, interceptors);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2014 the original author or authors.
|
||||
* Copyright 2002-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -288,11 +288,7 @@ class MessageBrokerBeanDefinitionParser implements BeanDefinitionParser {
|
||||
ManagedList<? super Object> interceptors = WebSocketNamespaceUtils.parseBeanSubElements(interceptorsElement, context);
|
||||
String allowedOriginsAttribute = element.getAttribute("allowed-origins");
|
||||
List<String> allowedOrigins = Arrays.asList(StringUtils.tokenizeToStringArray(allowedOriginsAttribute, ","));
|
||||
if (!allowedOrigins.isEmpty()) {
|
||||
OriginHandshakeInterceptor interceptor = new OriginHandshakeInterceptor();
|
||||
interceptor.setAllowedOrigins(allowedOrigins);
|
||||
interceptors.add(interceptor);
|
||||
}
|
||||
interceptors.add(new OriginHandshakeInterceptor(allowedOrigins));
|
||||
ConstructorArgumentValues cavs = new ConstructorArgumentValues();
|
||||
cavs.addIndexedArgumentValue(0, subProtoHandler);
|
||||
if (handshakeHandler != null) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2014 the original author or authors.
|
||||
* Copyright 2002-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -105,12 +105,8 @@ class WebSocketNamespaceUtils {
|
||||
ManagedList<? super Object> interceptors = WebSocketNamespaceUtils.parseBeanSubElements(interceptorsElement, context);
|
||||
String allowedOriginsAttribute = element.getAttribute("allowed-origins");
|
||||
List<String> allowedOrigins = Arrays.asList(StringUtils.tokenizeToStringArray(allowedOriginsAttribute, ","));
|
||||
if (!allowedOrigins.isEmpty()) {
|
||||
sockJsServiceDef.getPropertyValues().add("allowedOrigins", allowedOrigins);
|
||||
OriginHandshakeInterceptor interceptor = new OriginHandshakeInterceptor();
|
||||
interceptor.setAllowedOrigins(allowedOrigins);
|
||||
interceptors.add(interceptor);
|
||||
}
|
||||
sockJsServiceDef.getPropertyValues().add("allowedOrigins", allowedOrigins);
|
||||
interceptors.add(new OriginHandshakeInterceptor(allowedOrigins));
|
||||
sockJsServiceDef.getPropertyValues().add("handshakeInterceptors", interceptors);
|
||||
|
||||
String attrValue = sockJsElement.getAttribute("name");
|
||||
|
||||
@@ -88,11 +88,10 @@ public abstract class AbstractWebSocketHandlerRegistration<M> implements WebSock
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebSocketHandlerRegistration setAllowedOrigins(String... origins) {
|
||||
Assert.notEmpty(origins, "No allowed origin specified");
|
||||
public WebSocketHandlerRegistration setAllowedOrigins(String... allowedOrigins) {
|
||||
this.allowedOrigins.clear();
|
||||
if (!ObjectUtils.isEmpty(origins)) {
|
||||
this.allowedOrigins.addAll(Arrays.asList(origins));
|
||||
if (!ObjectUtils.isEmpty(allowedOrigins)) {
|
||||
this.allowedOrigins.addAll(Arrays.asList(allowedOrigins));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
@@ -117,11 +116,7 @@ public abstract class AbstractWebSocketHandlerRegistration<M> implements WebSock
|
||||
protected HandshakeInterceptor[] getInterceptors() {
|
||||
List<HandshakeInterceptor> interceptors = new ArrayList<HandshakeInterceptor>();
|
||||
interceptors.addAll(this.interceptors);
|
||||
if (!this.allowedOrigins.isEmpty()) {
|
||||
OriginHandshakeInterceptor interceptor = new OriginHandshakeInterceptor();
|
||||
interceptor.setAllowedOrigins(this.allowedOrigins);
|
||||
interceptors.add(interceptor);
|
||||
}
|
||||
interceptors.add(new OriginHandshakeInterceptor(this.allowedOrigins));
|
||||
return interceptors.toArray(new HandshakeInterceptor[interceptors.size()]);
|
||||
}
|
||||
|
||||
|
||||
@@ -206,6 +206,17 @@ public class SockJsServiceRegistration {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 4.1.2
|
||||
*/
|
||||
protected SockJsServiceRegistration setAllowedOrigins(String... allowedOrigins) {
|
||||
this.allowedOrigins.clear();
|
||||
if (!ObjectUtils.isEmpty(allowedOrigins)) {
|
||||
this.allowedOrigins.addAll(Arrays.asList(allowedOrigins));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* This option can be used to disable automatic addition of CORS headers for
|
||||
* SockJS requests.
|
||||
@@ -229,17 +240,6 @@ public class SockJsServiceRegistration {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 4.1.2
|
||||
*/
|
||||
protected SockJsServiceRegistration setAllowedOrigins(String... origins) {
|
||||
this.allowedOrigins.clear();
|
||||
if (!ObjectUtils.isEmpty(origins)) {
|
||||
this.allowedOrigins.addAll(Arrays.asList(origins));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
protected SockJsService getSockJsService() {
|
||||
TransportHandlingSockJsService service = createSockJsService();
|
||||
service.setHandshakeInterceptors(this.interceptors);
|
||||
@@ -264,12 +264,12 @@ public class SockJsServiceRegistration {
|
||||
if (this.webSocketEnabled != null) {
|
||||
service.setWebSocketEnabled(this.webSocketEnabled);
|
||||
}
|
||||
if (this.allowedOrigins != null) {
|
||||
service.setAllowedOrigins(this.allowedOrigins);
|
||||
}
|
||||
if (this.suppressCors != null) {
|
||||
service.setSuppressCors(this.suppressCors);
|
||||
}
|
||||
if (!this.allowedOrigins.isEmpty()) {
|
||||
service.setAllowedOrigins(this.allowedOrigins);
|
||||
}
|
||||
if (this.messageCodec != null) {
|
||||
service.setMessageCodec(this.messageCodec);
|
||||
}
|
||||
|
||||
@@ -52,8 +52,8 @@ public interface StompWebSocketEndpointRegistration {
|
||||
* As a consequence, IE 6 to 9 are not supported when origins are restricted.
|
||||
*
|
||||
* <p>Each provided allowed origin must start by "http://", "https://" or be "*"
|
||||
* (means that all origins are allowed). Empty allowed origin list is not supported.
|
||||
* By default, all origins are allowed.
|
||||
* (means that all origins are allowed). By default, only same origin requests are
|
||||
* allowed (empty list).
|
||||
*
|
||||
* @since 4.1.2
|
||||
* @see <a href="https://tools.ietf.org/html/rfc6454">RFC 6454: The Web Origin Concept</a>
|
||||
|
||||
@@ -85,10 +85,11 @@ public class WebMvcStompWebSocketEndpointRegistration implements StompWebSocketE
|
||||
}
|
||||
|
||||
@Override
|
||||
public StompWebSocketEndpointRegistration setAllowedOrigins(String... origins) {
|
||||
Assert.notEmpty(origins, "No allowed origin specified");
|
||||
public StompWebSocketEndpointRegistration setAllowedOrigins(String... allowedOrigins) {
|
||||
this.allowedOrigins.clear();
|
||||
this.allowedOrigins.addAll(Arrays.asList(origins));
|
||||
if (!ObjectUtils.isEmpty(allowedOrigins)) {
|
||||
this.allowedOrigins.addAll(Arrays.asList(allowedOrigins));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -112,11 +113,7 @@ public class WebMvcStompWebSocketEndpointRegistration implements StompWebSocketE
|
||||
protected HandshakeInterceptor[] getInterceptors() {
|
||||
List<HandshakeInterceptor> interceptors = new ArrayList<HandshakeInterceptor>();
|
||||
interceptors.addAll(this.interceptors);
|
||||
if (!this.allowedOrigins.isEmpty()) {
|
||||
OriginHandshakeInterceptor interceptor = new OriginHandshakeInterceptor();
|
||||
interceptor.setAllowedOrigins(this.allowedOrigins);
|
||||
interceptors.add(interceptor);
|
||||
}
|
||||
interceptors.add(new OriginHandshakeInterceptor(this.allowedOrigins));
|
||||
return interceptors.toArray(new HandshakeInterceptor[interceptors.size()]);
|
||||
}
|
||||
|
||||
|
||||
@@ -54,8 +54,8 @@ public interface WebSocketHandlerRegistration {
|
||||
* As a consequence, IE 6 to 9 are not supported when origins are restricted.
|
||||
*
|
||||
* <p>Each provided allowed origin must start by "http://", "https://" or be "*"
|
||||
* (means that all origins are allowed). Empty allowed origin list is not supported.
|
||||
* By default, all origins are allowed.
|
||||
* (means that all origins are allowed). By default, only same origin requests are
|
||||
* allowed (empty list).
|
||||
*
|
||||
* @since 4.1.2
|
||||
* @see <a href="https://tools.ietf.org/html/rfc6454">RFC 6454: The Web Origin Concept</a>
|
||||
|
||||
@@ -31,6 +31,7 @@ import org.springframework.http.server.ServerHttpResponse;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.socket.WebSocketHandler;
|
||||
import org.springframework.web.socket.server.HandshakeInterceptor;
|
||||
import org.springframework.web.util.WebUtils;
|
||||
|
||||
/**
|
||||
* An interceptor to check request {@code Origin} header value against a collection of
|
||||
@@ -47,12 +48,22 @@ public class OriginHandshakeInterceptor implements HandshakeInterceptor {
|
||||
|
||||
|
||||
/**
|
||||
* Default constructor with no origin allowed.
|
||||
* Default constructor with only same origin requests allowed.
|
||||
*/
|
||||
public OriginHandshakeInterceptor() {
|
||||
this.allowedOrigins = new ArrayList<String>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor using the specified allowed origin values.
|
||||
*
|
||||
* @see #setAllowedOrigins(Collection)
|
||||
*/
|
||||
public OriginHandshakeInterceptor(Collection<String> allowedOrigins) {
|
||||
this();
|
||||
setAllowedOrigins(allowedOrigins);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure allowed {@code Origin} header values. This check is mostly designed for
|
||||
* browser clients. There is nothing preventing other types of client to modify the
|
||||
@@ -85,7 +96,7 @@ public class OriginHandshakeInterceptor implements HandshakeInterceptor {
|
||||
@Override
|
||||
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response,
|
||||
WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
|
||||
if (!isValidOrigin(request)) {
|
||||
if (!WebUtils.isValidOrigin(request, this.allowedOrigins)) {
|
||||
response.setStatusCode(HttpStatus.FORBIDDEN);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Handshake request rejected, Origin header value "
|
||||
@@ -96,17 +107,6 @@ public class OriginHandshakeInterceptor implements HandshakeInterceptor {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected boolean isValidOrigin(ServerHttpRequest request) {
|
||||
String origin = request.getHeaders().getOrigin();
|
||||
if (origin == null) {
|
||||
return true;
|
||||
}
|
||||
if (this.allowedOrigins.contains("*")) {
|
||||
return true;
|
||||
}
|
||||
return this.allowedOrigins.contains(origin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response,
|
||||
WebSocketHandler wsHandler, Exception exception) {
|
||||
|
||||
@@ -46,12 +46,16 @@ import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.socket.WebSocketHandler;
|
||||
import org.springframework.web.socket.sockjs.SockJsException;
|
||||
import org.springframework.web.socket.sockjs.SockJsService;
|
||||
import org.springframework.web.util.WebUtils;
|
||||
|
||||
/**
|
||||
* An abstract base class for {@link SockJsService} implementations that provides SockJS
|
||||
* path resolution and handling of static SockJS requests (e.g. "/info", "/iframe.html",
|
||||
* etc). Sub-classes must handle session URLs (i.e. transport-specific requests).
|
||||
*
|
||||
* By default, only same origin requests are allowed. Use {@link #setAllowedOrigins(List)}
|
||||
* to specify a list of allowed origins (a list containing "*" will allow all origins).
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Sebastien Deleuze
|
||||
* @since 4.0
|
||||
@@ -64,6 +68,8 @@ public abstract class AbstractSockJsService implements SockJsService {
|
||||
|
||||
private static final Random random = new Random();
|
||||
|
||||
private static final String XFRAME_OPTIONS_HEADER = "X-Frame-Options";
|
||||
|
||||
|
||||
protected final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
@@ -85,7 +91,7 @@ public abstract class AbstractSockJsService implements SockJsService {
|
||||
|
||||
private boolean webSocketEnabled = true;
|
||||
|
||||
private final List<String> allowedOrigins = new ArrayList<String>(Arrays.asList("*"));
|
||||
private final List<String> allowedOrigins = new ArrayList<String>();
|
||||
|
||||
private boolean suppressCors = false;
|
||||
|
||||
@@ -275,15 +281,14 @@ public abstract class AbstractSockJsService implements SockJsService {
|
||||
* As a consequence, IE 6 to 9 are not supported when origins are restricted.
|
||||
*
|
||||
* <p>Each provided allowed origin must start by "http://", "https://" or be "*"
|
||||
* (means that all origins are allowed). Empty allowed origin list is not supported.
|
||||
* By default, all origins are allowed.
|
||||
* (means that all origins are allowed).
|
||||
*
|
||||
* @since 4.1.2
|
||||
* @see <a href="https://tools.ietf.org/html/rfc6454">RFC 6454: The Web Origin Concept</a>
|
||||
* @see <a href="https://github.com/sockjs/sockjs-client#supported-transports-by-browser-html-served-from-http-or-https">SockJS supported transports by browser</a>
|
||||
*/
|
||||
public void setAllowedOrigins(List<String> allowedOrigins) {
|
||||
Assert.notEmpty(allowedOrigins, "Allowed origin List must not be empty");
|
||||
Assert.notNull(allowedOrigins, "Allowed origin List must not be null");
|
||||
for (String allowedOrigin : allowedOrigins) {
|
||||
Assert.isTrue(
|
||||
allowedOrigin.equals("*") || allowedOrigin.startsWith("http://") ||
|
||||
@@ -360,6 +365,9 @@ public abstract class AbstractSockJsService implements SockJsService {
|
||||
response.setStatusCode(HttpStatus.NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
if (this.allowedOrigins.isEmpty()) {
|
||||
response.getHeaders().add(XFRAME_OPTIONS_HEADER, "SAMEORIGIN");
|
||||
}
|
||||
logger.debug(requestInfo);
|
||||
this.iframeHandler.handle(request, response);
|
||||
}
|
||||
@@ -438,13 +446,12 @@ public abstract class AbstractSockJsService implements SockJsService {
|
||||
HttpHeaders requestHeaders = request.getHeaders();
|
||||
HttpHeaders responseHeaders = response.getHeaders();
|
||||
String origin = requestHeaders.getOrigin();
|
||||
String host = requestHeaders.getFirst(HttpHeaders.HOST);
|
||||
|
||||
if (origin == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!this.allowedOrigins.contains("*") && !this.allowedOrigins.contains(origin)) {
|
||||
if (!WebUtils.isValidOrigin(request, this.allowedOrigins)) {
|
||||
logger.debug("Request rejected, Origin header value " + origin + " not allowed");
|
||||
response.setStatusCode(HttpStatus.FORBIDDEN);
|
||||
return false;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2014 the original author or authors.
|
||||
* Copyright 2002-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -45,9 +45,9 @@ public enum TransportType {
|
||||
|
||||
XHR_STREAMING("xhr_streaming", HttpMethod.POST, "cors", "jsessionid", "no_cache"),
|
||||
|
||||
EVENT_SOURCE("eventsource", HttpMethod.GET, "jsessionid", "no_cache"),
|
||||
EVENT_SOURCE("eventsource", HttpMethod.GET, "origin", "jsessionid", "no_cache"),
|
||||
|
||||
HTML_FILE("htmlfile", HttpMethod.GET, "jsessionid", "no_cache");
|
||||
HTML_FILE("htmlfile", HttpMethod.GET, "cors", "jsessionid", "no_cache");
|
||||
|
||||
|
||||
private final String value;
|
||||
|
||||
Reference in New Issue
Block a user