Skip to content

Commit 23fa37b

Browse files
sdeleuzejhoeller
authored andcommitted
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)
1 parent cc78d40 commit 23fa37b

20 files changed

+363
-123
lines changed

spring-web/src/main/java/org/springframework/web/util/WebUtils.java

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2014 the original author or authors.
2+
* Copyright 2002-2015 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@
1919
import java.io.File;
2020
import java.io.FileNotFoundException;
2121
import java.util.Enumeration;
22+
import java.util.List;
2223
import java.util.Map;
2324
import java.util.StringTokenizer;
2425
import java.util.TreeMap;
@@ -32,6 +33,7 @@
3233
import javax.servlet.http.HttpServletResponse;
3334
import javax.servlet.http.HttpSession;
3435

36+
import org.springframework.http.server.ServerHttpRequest;
3537
import org.springframework.util.Assert;
3638
import org.springframework.util.LinkedMultiValueMap;
3739
import org.springframework.util.MultiValueMap;
@@ -43,6 +45,7 @@
4345
*
4446
* @author Rod Johnson
4547
* @author Juergen Hoeller
48+
* @author Sebastien Deleuze
4649
*/
4750
public abstract class WebUtils {
4851

@@ -765,4 +768,47 @@ public static MultiValueMap<String, String> parseMatrixVariables(String matrixVa
765768
}
766769
return result;
767770
}
771+
772+
/**
773+
* Check the given request origin against a list of allowed origins.
774+
* A list containing "*" means that all origins are allowed.
775+
* An empty list means only same origin is allowed.
776+
*
777+
* @return true if the request origin is valid, false otherwise
778+
* @since 4.1.5
779+
* @see <a href="https://tools.ietf.org/html/rfc6454">RFC 6454: The Web Origin Concept</a>
780+
*/
781+
public static boolean isValidOrigin(ServerHttpRequest request, List<String> allowedOrigins) {
782+
Assert.notNull(request, "Request must not be null");
783+
Assert.notNull(allowedOrigins, "Allowed origins must not be null");
784+
785+
String origin = request.getHeaders().getOrigin();
786+
if (origin == null || allowedOrigins.contains("*")) {
787+
return true;
788+
}
789+
else if (allowedOrigins.isEmpty()) {
790+
UriComponents originComponents = UriComponentsBuilder.fromHttpUrl(origin).build();
791+
UriComponents requestComponents = UriComponentsBuilder.fromHttpRequest(request).build();
792+
int originPort = getPort(originComponents);
793+
int requestPort = getPort(requestComponents);
794+
return originComponents.getHost().equals(requestComponents.getHost()) && (originPort == requestPort);
795+
}
796+
else {
797+
return allowedOrigins.contains(origin);
798+
}
799+
}
800+
801+
private static int getPort(UriComponents component) {
802+
int port = component.getPort();
803+
if (port == -1) {
804+
if ("http".equals(component.getScheme())) {
805+
port = 80;
806+
}
807+
else if ("https".equals(component.getScheme())) {
808+
port = 443;
809+
}
810+
}
811+
return port;
812+
}
813+
768814
}

spring-web/src/test/java/org/springframework/web/util/WebUtilsTests.java

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2008 the original author or authors.
2+
* Copyright 2002-2015 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,12 +16,18 @@
1616

1717
package org.springframework.web.util;
1818

19+
import java.util.ArrayList;
1920
import java.util.Arrays;
2021
import java.util.HashMap;
22+
import java.util.List;
2123
import java.util.Map;
2224

2325
import org.junit.Test;
2426

27+
import org.springframework.http.HttpHeaders;
28+
import org.springframework.http.server.ServerHttpRequest;
29+
import org.springframework.http.server.ServletServerHttpRequest;
30+
import org.springframework.mock.web.test.MockHttpServletRequest;
2531
import org.springframework.util.MultiValueMap;
2632

2733
import static org.junit.Assert.*;
@@ -30,6 +36,7 @@
3036
* @author Juergen Hoeller
3137
* @author Arjen Poutsma
3238
* @author Rossen Stoyanchev
39+
* @author Sebastien Deleuze
3340
*/
3441
public class WebUtilsTests {
3542

@@ -98,4 +105,57 @@ public void parseMatrixVariablesString() {
98105
assertEquals(Arrays.asList("red", "blue", "green"), variables.get("colors"));
99106
}
100107

108+
@Test
109+
public void isValidOrigin() {
110+
List<String> allowedOrigins = new ArrayList<>();
111+
MockHttpServletRequest servletRequest = new MockHttpServletRequest();
112+
ServerHttpRequest request = new ServletServerHttpRequest(servletRequest);
113+
114+
servletRequest.setServerName("mydomain1.com");
115+
request.getHeaders().set(HttpHeaders.ORIGIN, "http://mydomain1.com");
116+
assertTrue(WebUtils.isValidOrigin(request, allowedOrigins));
117+
118+
servletRequest.setServerName("mydomain1.com");
119+
request.getHeaders().set(HttpHeaders.ORIGIN, "http://mydomain1.com:80");
120+
assertTrue(WebUtils.isValidOrigin(request, allowedOrigins));
121+
122+
servletRequest.setServerName("mydomain1.com");
123+
servletRequest.setServerPort(443);
124+
request.getHeaders().set(HttpHeaders.ORIGIN, "https://mydomain1.com");
125+
assertTrue(WebUtils.isValidOrigin(request, allowedOrigins));
126+
127+
servletRequest.setServerName("mydomain1.com");
128+
servletRequest.setServerPort(443);
129+
request.getHeaders().set(HttpHeaders.ORIGIN, "https://mydomain1.com:443");
130+
assertTrue(WebUtils.isValidOrigin(request, allowedOrigins));
131+
132+
servletRequest.setServerName("mydomain1.com");
133+
servletRequest.setServerPort(123);
134+
request.getHeaders().set(HttpHeaders.ORIGIN, "http://mydomain1.com:123");
135+
assertTrue(WebUtils.isValidOrigin(request, allowedOrigins));
136+
137+
servletRequest.setServerName("mydomain1.com");
138+
request.getHeaders().set(HttpHeaders.ORIGIN, "http://mydomain2.com");
139+
assertFalse(WebUtils.isValidOrigin(request, allowedOrigins));
140+
141+
servletRequest.setServerName("mydomain1.com");
142+
request.getHeaders().set(HttpHeaders.ORIGIN, "https://mydomain1.com");
143+
assertFalse(WebUtils.isValidOrigin(request, allowedOrigins));
144+
145+
allowedOrigins = Arrays.asList("*");
146+
servletRequest.setServerName("mydomain1.com");
147+
request.getHeaders().set(HttpHeaders.ORIGIN, "http://mydomain2.com");
148+
assertTrue(WebUtils.isValidOrigin(request, allowedOrigins));
149+
150+
allowedOrigins = Arrays.asList("http://mydomain1.com");
151+
servletRequest.setServerName("mydomain2.com");
152+
request.getHeaders().set(HttpHeaders.ORIGIN, "http://mydomain1.com");
153+
assertTrue(WebUtils.isValidOrigin(request, allowedOrigins));
154+
155+
allowedOrigins = Arrays.asList("http://mydomain1.com");
156+
servletRequest.setServerName("mydomain2.com");
157+
request.getHeaders().set(HttpHeaders.ORIGIN, "http://mydomain3.com");
158+
assertFalse(WebUtils.isValidOrigin(request, allowedOrigins));
159+
}
160+
101161
}

spring-websocket/src/main/java/org/springframework/web/socket/config/HandlersBeanDefinitionParser.java

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2014 the original author or authors.
2+
* Copyright 2002-2015 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -83,11 +83,7 @@ public BeanDefinition parse(Element element, ParserContext context) {
8383
ManagedList<? super Object> interceptors = WebSocketNamespaceUtils.parseBeanSubElements(interceptorsElement, context);
8484
String allowedOriginsAttribute = element.getAttribute("allowed-origins");
8585
List<String> allowedOrigins = Arrays.asList(StringUtils.tokenizeToStringArray(allowedOriginsAttribute, ","));
86-
if (!allowedOrigins.isEmpty()) {
87-
OriginHandshakeInterceptor interceptor = new OriginHandshakeInterceptor();
88-
interceptor.setAllowedOrigins(allowedOrigins);
89-
interceptors.add(interceptor);
90-
}
86+
interceptors.add(new OriginHandshakeInterceptor(allowedOrigins));
9187
strategy = new WebSocketHandlerMappingStrategy(handshakeHandler, interceptors);
9288
}
9389

spring-websocket/src/main/java/org/springframework/web/socket/config/MessageBrokerBeanDefinitionParser.java

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2014 the original author or authors.
2+
* Copyright 2002-2015 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -288,11 +288,7 @@ private RuntimeBeanReference registerRequestHandler(Element element, RuntimeBean
288288
ManagedList<? super Object> interceptors = WebSocketNamespaceUtils.parseBeanSubElements(interceptorsElement, context);
289289
String allowedOriginsAttribute = element.getAttribute("allowed-origins");
290290
List<String> allowedOrigins = Arrays.asList(StringUtils.tokenizeToStringArray(allowedOriginsAttribute, ","));
291-
if (!allowedOrigins.isEmpty()) {
292-
OriginHandshakeInterceptor interceptor = new OriginHandshakeInterceptor();
293-
interceptor.setAllowedOrigins(allowedOrigins);
294-
interceptors.add(interceptor);
295-
}
291+
interceptors.add(new OriginHandshakeInterceptor(allowedOrigins));
296292
ConstructorArgumentValues cavs = new ConstructorArgumentValues();
297293
cavs.addIndexedArgumentValue(0, subProtoHandler);
298294
if (handshakeHandler != null) {

spring-websocket/src/main/java/org/springframework/web/socket/config/WebSocketNamespaceUtils.java

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2014 the original author or authors.
2+
* Copyright 2002-2015 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -105,12 +105,8 @@ else if (handshakeHandler != null) {
105105
ManagedList<? super Object> interceptors = WebSocketNamespaceUtils.parseBeanSubElements(interceptorsElement, context);
106106
String allowedOriginsAttribute = element.getAttribute("allowed-origins");
107107
List<String> allowedOrigins = Arrays.asList(StringUtils.tokenizeToStringArray(allowedOriginsAttribute, ","));
108-
if (!allowedOrigins.isEmpty()) {
109-
sockJsServiceDef.getPropertyValues().add("allowedOrigins", allowedOrigins);
110-
OriginHandshakeInterceptor interceptor = new OriginHandshakeInterceptor();
111-
interceptor.setAllowedOrigins(allowedOrigins);
112-
interceptors.add(interceptor);
113-
}
108+
sockJsServiceDef.getPropertyValues().add("allowedOrigins", allowedOrigins);
109+
interceptors.add(new OriginHandshakeInterceptor(allowedOrigins));
114110
sockJsServiceDef.getPropertyValues().add("handshakeInterceptors", interceptors);
115111

116112
String attrValue = sockJsElement.getAttribute("name");

spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/AbstractWebSocketHandlerRegistration.java

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -88,11 +88,10 @@ public WebSocketHandlerRegistration addInterceptors(HandshakeInterceptor... inte
8888
}
8989

9090
@Override
91-
public WebSocketHandlerRegistration setAllowedOrigins(String... origins) {
92-
Assert.notEmpty(origins, "No allowed origin specified");
91+
public WebSocketHandlerRegistration setAllowedOrigins(String... allowedOrigins) {
9392
this.allowedOrigins.clear();
94-
if (!ObjectUtils.isEmpty(origins)) {
95-
this.allowedOrigins.addAll(Arrays.asList(origins));
93+
if (!ObjectUtils.isEmpty(allowedOrigins)) {
94+
this.allowedOrigins.addAll(Arrays.asList(allowedOrigins));
9695
}
9796
return this;
9897
}
@@ -117,11 +116,7 @@ public SockJsServiceRegistration withSockJS() {
117116
protected HandshakeInterceptor[] getInterceptors() {
118117
List<HandshakeInterceptor> interceptors = new ArrayList<HandshakeInterceptor>();
119118
interceptors.addAll(this.interceptors);
120-
if (!this.allowedOrigins.isEmpty()) {
121-
OriginHandshakeInterceptor interceptor = new OriginHandshakeInterceptor();
122-
interceptor.setAllowedOrigins(this.allowedOrigins);
123-
interceptors.add(interceptor);
124-
}
119+
interceptors.add(new OriginHandshakeInterceptor(this.allowedOrigins));
125120
return interceptors.toArray(new HandshakeInterceptor[interceptors.size()]);
126121
}
127122

spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/SockJsServiceRegistration.java

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,17 @@ public SockJsServiceRegistration setInterceptors(HandshakeInterceptor... interce
206206
return this;
207207
}
208208

209+
/**
210+
* @since 4.1.2
211+
*/
212+
protected SockJsServiceRegistration setAllowedOrigins(String... allowedOrigins) {
213+
this.allowedOrigins.clear();
214+
if (!ObjectUtils.isEmpty(allowedOrigins)) {
215+
this.allowedOrigins.addAll(Arrays.asList(allowedOrigins));
216+
}
217+
return this;
218+
}
219+
209220
/**
210221
* This option can be used to disable automatic addition of CORS headers for
211222
* SockJS requests.
@@ -229,17 +240,6 @@ public SockJsServiceRegistration setMessageCodec(SockJsMessageCodec codec) {
229240
return this;
230241
}
231242

232-
/**
233-
* @since 4.1.2
234-
*/
235-
protected SockJsServiceRegistration setAllowedOrigins(String... origins) {
236-
this.allowedOrigins.clear();
237-
if (!ObjectUtils.isEmpty(origins)) {
238-
this.allowedOrigins.addAll(Arrays.asList(origins));
239-
}
240-
return this;
241-
}
242-
243243
protected SockJsService getSockJsService() {
244244
TransportHandlingSockJsService service = createSockJsService();
245245
service.setHandshakeInterceptors(this.interceptors);
@@ -264,12 +264,12 @@ protected SockJsService getSockJsService() {
264264
if (this.webSocketEnabled != null) {
265265
service.setWebSocketEnabled(this.webSocketEnabled);
266266
}
267+
if (this.allowedOrigins != null) {
268+
service.setAllowedOrigins(this.allowedOrigins);
269+
}
267270
if (this.suppressCors != null) {
268271
service.setSuppressCors(this.suppressCors);
269272
}
270-
if (!this.allowedOrigins.isEmpty()) {
271-
service.setAllowedOrigins(this.allowedOrigins);
272-
}
273273
if (this.messageCodec != null) {
274274
service.setMessageCodec(this.messageCodec);
275275
}

spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/StompWebSocketEndpointRegistration.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@ public interface StompWebSocketEndpointRegistration {
5252
* As a consequence, IE 6 to 9 are not supported when origins are restricted.
5353
*
5454
* <p>Each provided allowed origin must start by "http://", "https://" or be "*"
55-
* (means that all origins are allowed). Empty allowed origin list is not supported.
56-
* By default, all origins are allowed.
55+
* (means that all origins are allowed). By default, only same origin requests are
56+
* allowed (empty list).
5757
*
5858
* @since 4.1.2
5959
* @see <a href="https://tools.ietf.org/html/rfc6454">RFC 6454: The Web Origin Concept</a>

spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/WebMvcStompWebSocketEndpointRegistration.java

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -85,10 +85,11 @@ public StompWebSocketEndpointRegistration addInterceptors(HandshakeInterceptor..
8585
}
8686

8787
@Override
88-
public StompWebSocketEndpointRegistration setAllowedOrigins(String... origins) {
89-
Assert.notEmpty(origins, "No allowed origin specified");
88+
public StompWebSocketEndpointRegistration setAllowedOrigins(String... allowedOrigins) {
9089
this.allowedOrigins.clear();
91-
this.allowedOrigins.addAll(Arrays.asList(origins));
90+
if (!ObjectUtils.isEmpty(allowedOrigins)) {
91+
this.allowedOrigins.addAll(Arrays.asList(allowedOrigins));
92+
}
9293
return this;
9394
}
9495

@@ -112,11 +113,7 @@ public SockJsServiceRegistration withSockJS() {
112113
protected HandshakeInterceptor[] getInterceptors() {
113114
List<HandshakeInterceptor> interceptors = new ArrayList<HandshakeInterceptor>();
114115
interceptors.addAll(this.interceptors);
115-
if (!this.allowedOrigins.isEmpty()) {
116-
OriginHandshakeInterceptor interceptor = new OriginHandshakeInterceptor();
117-
interceptor.setAllowedOrigins(this.allowedOrigins);
118-
interceptors.add(interceptor);
119-
}
116+
interceptors.add(new OriginHandshakeInterceptor(this.allowedOrigins));
120117
return interceptors.toArray(new HandshakeInterceptor[interceptors.size()]);
121118
}
122119

spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/WebSocketHandlerRegistration.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,8 @@ public interface WebSocketHandlerRegistration {
5454
* As a consequence, IE 6 to 9 are not supported when origins are restricted.
5555
*
5656
* <p>Each provided allowed origin must start by "http://", "https://" or be "*"
57-
* (means that all origins are allowed). Empty allowed origin list is not supported.
58-
* By default, all origins are allowed.
57+
* (means that all origins are allowed). By default, only same origin requests are
58+
* allowed (empty list).
5959
*
6060
* @since 4.1.2
6161
* @see <a href="https://tools.ietf.org/html/rfc6454">RFC 6454: The Web Origin Concept</a>

0 commit comments

Comments
 (0)