Skip to content

Commit cc78d40

Browse files
sdeleuzejhoeller
authored andcommitted
Fix SockJS origin check
This commit introduces the following changes: - Requests without Origin header are not rejected anymore - Disable Iframe when allowedOrigins is not empty and not equals to * - The Iframe is not cached anymore in order to have a reliable origin check - allowedOrigins must not be null or empty - allowedOrigins format is now validated (should be * or start by http(s)://) Issue: SPR-12660 (cherry picked from commit 9b3319b)
1 parent 1dc3932 commit cc78d40

File tree

12 files changed

+198
-117
lines changed

12 files changed

+198
-117
lines changed

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

Lines changed: 2 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.
@@ -89,6 +89,7 @@ public WebSocketHandlerRegistration addInterceptors(HandshakeInterceptor... inte
8989

9090
@Override
9191
public WebSocketHandlerRegistration setAllowedOrigins(String... origins) {
92+
Assert.notEmpty(origins, "No allowed origin specified");
9293
this.allowedOrigins.clear();
9394
if (!ObjectUtils.isEmpty(origins)) {
9495
this.allowedOrigins.addAll(Arrays.asList(origins));

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

Lines changed: 12 additions & 8 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.
@@ -43,16 +43,20 @@ public interface StompWebSocketEndpointRegistration {
4343
StompWebSocketEndpointRegistration addInterceptors(HandshakeInterceptor... interceptors);
4444

4545
/**
46-
* Configure allowed {@code Origin} header values. This check is mostly designed for browser
47-
* clients. There is noting preventing other types of client to modify the Origin header value.
46+
* Configure allowed {@code Origin} header values. This check is mostly designed for
47+
* browser clients. There is nothing preventing other types of client to modify the
48+
* {@code Origin} header value.
4849
*
49-
* <p>When SockJS is enabled and allowed origins are restricted, transport types that do not
50-
* use {@code Origin} headers for cross origin requests (jsonp-polling, iframe-xhr-polling,
51-
* iframe-eventsource and iframe-htmlfile) are disabled. As a consequence, IE6/IE7 won't be
52-
* supported anymore and IE8/IE9 will only be supported without cookies.
50+
* <p>When SockJS is enabled and origins are restricted, transport types that do not
51+
* allow to check request origin (JSONP and Iframe based transports) are disabled.
52+
* As a consequence, IE 6 to 9 are not supported when origins are restricted.
53+
*
54+
* <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.
5357
*
54-
* <p>By default, all origins are allowed.
5558
* @since 4.1.2
59+
* @see <a href="https://tools.ietf.org/html/rfc6454">RFC 6454: The Web Origin Concept</a>
5660
* @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>
5761
*/
5862
StompWebSocketEndpointRegistration setAllowedOrigins(String... origins);

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

Lines changed: 3 additions & 4 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.
@@ -86,10 +86,9 @@ public StompWebSocketEndpointRegistration addInterceptors(HandshakeInterceptor..
8686

8787
@Override
8888
public StompWebSocketEndpointRegistration setAllowedOrigins(String... origins) {
89+
Assert.notEmpty(origins, "No allowed origin specified");
8990
this.allowedOrigins.clear();
90-
if (!ObjectUtils.isEmpty(origins)) {
91-
this.allowedOrigins.addAll(Arrays.asList(origins));
92-
}
91+
this.allowedOrigins.addAll(Arrays.asList(origins));
9392
return this;
9493
}
9594

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

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2013 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.
@@ -45,17 +45,20 @@ public interface WebSocketHandlerRegistration {
4545
WebSocketHandlerRegistration addInterceptors(HandshakeInterceptor... interceptors);
4646

4747
/**
48-
* Configure allowed {@code Origin} header values. This check is mostly designed for browser
49-
* clients. There is noting preventing other types of client to modify the Origin header value.
48+
* Configure allowed {@code Origin} header values. This check is mostly designed for
49+
* browser clients. There is nothing preventing other types of client to modify the
50+
* {@code Origin} header value.
5051
*
51-
* <p>When SockJS is enabled and allowed origins are restricted, transport types that do not
52-
* use {@code Origin} headers for cross origin requests (jsonp-polling, iframe-xhr-polling,
53-
* iframe-eventsource and iframe-htmlfile) are disabled. As a consequence, IE6/IE7 won't be
54-
* supported anymore and IE8/IE9 will only be supported without cookies.
52+
* <p>When SockJS is enabled and origins are restricted, transport types that do not
53+
* allow to check request origin (JSONP and Iframe based transports) are disabled.
54+
* As a consequence, IE 6 to 9 are not supported when origins are restricted.
5555
*
56-
* <p>By default, all origins are allowed.
56+
* <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.
5759
*
5860
* @since 4.1.2
61+
* @see <a href="https://tools.ietf.org/html/rfc6454">RFC 6454: The Web Origin Concept</a>
5962
* @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>
6063
*/
6164
WebSocketHandlerRegistration setAllowedOrigins(String... origins);

spring-websocket/src/main/java/org/springframework/web/socket/server/support/OriginHandshakeInterceptor.java

Lines changed: 34 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.
@@ -18,6 +18,7 @@
1818

1919
import java.util.ArrayList;
2020
import java.util.Collection;
21+
import java.util.Collections;
2122
import java.util.List;
2223
import java.util.Map;
2324

@@ -27,6 +28,7 @@
2728
import org.springframework.http.HttpStatus;
2829
import org.springframework.http.server.ServerHttpRequest;
2930
import org.springframework.http.server.ServerHttpResponse;
31+
import org.springframework.util.Assert;
3032
import org.springframework.web.socket.WebSocketHandler;
3133
import org.springframework.web.socket.server.HandshakeInterceptor;
3234

@@ -52,13 +54,32 @@ public OriginHandshakeInterceptor() {
5254
}
5355

5456
/**
55-
* Use this property to define a collection of allowed origins.
57+
* Configure allowed {@code Origin} header values. This check is mostly designed for
58+
* browser clients. There is nothing preventing other types of client to modify the
59+
* {@code Origin} header value.
60+
*
61+
* <p>Each provided allowed origin must start by "http://", "https://" or be "*"
62+
* (means that all origins are allowed).
63+
*
64+
* @see <a href="https://tools.ietf.org/html/rfc6454">RFC 6454: The Web Origin Concept</a>
5665
*/
5766
public void setAllowedOrigins(Collection<String> allowedOrigins) {
58-
this.allowedOrigins.clear();
59-
if (allowedOrigins != null) {
60-
this.allowedOrigins.addAll(allowedOrigins);
67+
Assert.notNull(allowedOrigins, "Allowed origin Collection must not be null");
68+
for (String allowedOrigin : allowedOrigins) {
69+
Assert.isTrue(allowedOrigin.equals("*") || allowedOrigin.startsWith("http://") ||
70+
allowedOrigin.startsWith("https://"), "Invalid allowed origin provided: \"" +
71+
allowedOrigin + "\". It must start with \"http://\", \"https://\" or be \"*\"");
6172
}
73+
this.allowedOrigins.clear();
74+
this.allowedOrigins.addAll(allowedOrigins);
75+
}
76+
77+
/**
78+
* @see #setAllowedOrigins(Collection)
79+
* @since 4.1.5
80+
*/
81+
public Collection<String> getAllowedOrigins() {
82+
return Collections.unmodifiableList(this.allowedOrigins);
6283
}
6384

6485
@Override
@@ -76,7 +97,14 @@ public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse res
7697
}
7798

7899
protected boolean isValidOrigin(ServerHttpRequest request) {
79-
return this.allowedOrigins.contains(request.getHeaders().getOrigin());
100+
String origin = request.getHeaders().getOrigin();
101+
if (origin == null) {
102+
return true;
103+
}
104+
if (this.allowedOrigins.contains("*")) {
105+
return true;
106+
}
107+
return this.allowedOrigins.contains(origin);
80108
}
81109

82110
@Override

spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/AbstractSockJsService.java

Lines changed: 36 additions & 15 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.
@@ -266,32 +266,42 @@ public boolean isWebSocketEnabled() {
266266
}
267267

268268
/**
269-
* Configure allowed {@code Origin} header values. This check is mostly designed for browser
270-
* clients. There is noting preventing other types of client to modify the Origin header value.
269+
* Configure allowed {@code Origin} header values. This check is mostly designed for
270+
* browser clients. There is nothing preventing other types of client to modify the
271+
* {@code Origin} header value.
271272
*
272-
* <p>When SockJS is enabled and allowed origins are restricted, transport types that do not
273-
* use {@code Origin} headers for cross origin requests (jsonp-polling, iframe-xhr-polling,
274-
* iframe-eventsource and iframe-htmlfile) are disabled. As a consequence, IE6/IE7 won't be
275-
* supported anymore and IE8/IE9 will only be supported without cookies.
273+
* <p>When SockJS is enabled and origins are restricted, transport types that do not
274+
* allow to check request origin (JSONP and Iframe based transports) are disabled.
275+
* As a consequence, IE 6 to 9 are not supported when origins are restricted.
276276
*
277-
* <p>By default, all origins are allowed.
277+
* <p>Each provided allowed origin must start by "http://", "https://" or be "*"
278+
* (means that all origins are allowed). Empty allowed origin list is not supported.
279+
* By default, all origins are allowed.
278280
*
279281
* @since 4.1.2
282+
* @see <a href="https://tools.ietf.org/html/rfc6454">RFC 6454: The Web Origin Concept</a>
280283
* @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>
281284
*/
282285
public void setAllowedOrigins(List<String> allowedOrigins) {
283-
this.allowedOrigins.clear();
284-
if (allowedOrigins != null) {
285-
this.allowedOrigins.addAll(allowedOrigins);
286+
Assert.notEmpty(allowedOrigins, "Allowed origin List must not be empty");
287+
for (String allowedOrigin : allowedOrigins) {
288+
Assert.isTrue(
289+
allowedOrigin.equals("*") || allowedOrigin.startsWith("http://") ||
290+
allowedOrigin.startsWith("https://"),
291+
"Invalid allowed origin provided: \"" +
292+
allowedOrigin +
293+
"\". It must start with \"http://\", \"https://\" or be \"*\"");
286294
}
295+
this.allowedOrigins.clear();
296+
this.allowedOrigins.addAll(allowedOrigins);
287297
}
288298

289299
/**
290300
* @since 4.1.2
291301
* @see #setAllowedOrigins(List)
292302
*/
293303
public List<String> getAllowedOrigins() {
294-
return Collections.unmodifiableList(allowedOrigins);
304+
return Collections.unmodifiableList(this.allowedOrigins);
295305
}
296306

297307
/**
@@ -345,6 +355,11 @@ else if (sockJsPath.equals("/info")) {
345355
this.infoHandler.handle(request, response);
346356
}
347357
else if (sockJsPath.matches("/iframe[0-9-.a-z_]*.html")) {
358+
if (!this.allowedOrigins.isEmpty() && !this.allowedOrigins.contains("*")) {
359+
logger.debug("Iframe support is disabled when an origin check is required, ignoring " + requestInfo);
360+
response.setStatusCode(HttpStatus.NOT_FOUND);
361+
return;
362+
}
348363
logger.debug(requestInfo);
349364
this.iframeHandler.handle(request, response);
350365
}
@@ -423,8 +438,13 @@ protected boolean checkAndAddCorsHeaders(ServerHttpRequest request, ServerHttpRe
423438
HttpHeaders requestHeaders = request.getHeaders();
424439
HttpHeaders responseHeaders = response.getHeaders();
425440
String origin = requestHeaders.getOrigin();
441+
String host = requestHeaders.getFirst(HttpHeaders.HOST);
442+
443+
if (origin == null) {
444+
return true;
445+
}
426446

427-
if (!this.allowedOrigins.contains("*") && (origin == null || !this.allowedOrigins.contains(origin))) {
447+
if (!this.allowedOrigins.contains("*") && !this.allowedOrigins.contains(origin)) {
428448
logger.debug("Request rejected, Origin header value " + origin + " not allowed");
429449
response.setStatusCode(HttpStatus.FORBIDDEN);
430450
return false;
@@ -439,7 +459,7 @@ protected boolean checkAndAddCorsHeaders(ServerHttpRequest request, ServerHttpRe
439459
// See SPR-11919 and https://issues.jboss.org/browse/WFLY-3474
440460
}
441461

442-
if (!this.suppressCors && origin != null && !hasCorsResponseHeaders) {
462+
if (!this.suppressCors && !hasCorsResponseHeaders) {
443463
addCorsHeaders(request, response, httpMethods);
444464
}
445465
return true;
@@ -561,7 +581,8 @@ public void handle(ServerHttpRequest request, ServerHttpResponse response) throw
561581
response.getHeaders().setContentType(new MediaType("text", "html", UTF8_CHARSET));
562582
response.getHeaders().setContentLength(contentBytes.length);
563583

564-
addCacheHeaders(response);
584+
// No cache in order to check every time if IFrame are authorized
585+
addNoCacheHeaders(response);
565586
response.getHeaders().setETag(etagValue);
566587
response.getBody().write(contentBytes);
567588
}

spring-websocket/src/test/java/org/springframework/web/socket/AbstractHttpRequestTests.java

Lines changed: 3 additions & 2 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.
@@ -18,6 +18,7 @@
1818

1919
import org.junit.Before;
2020

21+
import org.springframework.http.HttpHeaders;
2122
import org.springframework.http.server.ServerHttpAsyncRequestControl;
2223
import org.springframework.http.server.ServerHttpRequest;
2324
import org.springframework.http.server.ServerHttpResponse;
@@ -55,7 +56,7 @@ protected void setRequest(String method, String requestUri) {
5556
}
5657

5758
protected void setOrigin(String origin) {
58-
this.servletRequest.addHeader("Origin", origin);
59+
this.request.getHeaders().add(HttpHeaders.ORIGIN, origin);
5960
}
6061

6162
protected void resetRequestAndResponse() {

spring-websocket/src/test/java/org/springframework/web/socket/config/annotation/WebMvcStompWebSocketEndpointRegistrationTests.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,12 @@ public void allowedOrigins() {
9090
assertEquals(OriginHandshakeInterceptor.class, requestHandler.getHandshakeInterceptors().get(0).getClass());
9191
}
9292

93+
@Test(expected = IllegalArgumentException.class)
94+
public void noAllowedOrigin() {
95+
WebMvcStompWebSocketEndpointRegistration registration = new WebMvcStompWebSocketEndpointRegistration(new String[] {"/foo"}, this.handler, this.scheduler);
96+
registration.setAllowedOrigins();
97+
}
98+
9399
@Test
94100
public void allowedOriginsWithSockJsService() {
95101
WebMvcStompWebSocketEndpointRegistration registration =

spring-websocket/src/test/java/org/springframework/web/socket/config/annotation/WebSocketHandlerRegistrationTests.java

Lines changed: 6 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.
@@ -93,6 +93,11 @@ public void interceptors() {
9393
assertArrayEquals(new HandshakeInterceptor[] {interceptor}, mapping.interceptors);
9494
}
9595

96+
@Test(expected = IllegalArgumentException.class)
97+
public void noAllowedOrigin() {
98+
this.registration.addHandler(Mockito.mock(WebSocketHandler.class), "/foo").setAllowedOrigins();
99+
}
100+
96101
@Test
97102
public void interceptorsWithAllowedOrigins() {
98103
WebSocketHandler handler = new TextWebSocketHandler();

0 commit comments

Comments
 (0)