Skip to content

Commit 966f95b

Browse files
committed
Revised TransportHandlingSockJsService for defensive transport checking and consistent logging
Issue: SPR-13545
1 parent af213a0 commit 966f95b

File tree

8 files changed

+108
-88
lines changed

8 files changed

+108
-88
lines changed

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

+39-21
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
import java.util.List;
2727
import java.util.Random;
2828
import java.util.concurrent.TimeUnit;
29-
3029
import javax.servlet.http.HttpServletRequest;
3130

3231
import org.apache.commons.logging.Log;
@@ -279,16 +278,13 @@ public boolean isWebSocketEnabled() {
279278
* Configure allowed {@code Origin} header values. This check is mostly
280279
* designed for browsers. There is nothing preventing other types of client
281280
* to modify the {@code Origin} header value.
282-
*
283281
* <p>When SockJS is enabled and origins are restricted, transport types
284282
* that do not allow to check request origin (JSONP and Iframe based
285283
* transports) are disabled. As a consequence, IE 6 to 9 are not supported
286284
* when origins are restricted.
287-
*
288285
* <p>Each provided allowed origin must have a scheme, and optionally a port
289286
* (e.g. "http://example.org", "http://example.org:9090"). An allowed origin
290287
* string may also be "*" in which case all origins are allowed.
291-
*
292288
* @since 4.1.2
293289
* @see <a href="https://tools.ietf.org/html/rfc6454">RFC 6454: The Web Origin Concept</a>
294290
* @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>
@@ -325,6 +321,7 @@ public boolean shouldSuppressCors() {
325321
return this.suppressCors;
326322
}
327323

324+
328325
/**
329326
* This method determines the SockJS path and handles SockJS static URLs.
330327
* Session URLs and raw WebSocket requests are delegated to abstract methods.
@@ -348,68 +345,84 @@ public final void handleRequest(ServerHttpRequest request, ServerHttpResponse re
348345
// As per SockJS protocol content-type can be ignored (it's always json)
349346
}
350347

351-
String requestInfo = logger.isDebugEnabled() ? request.getMethod() + " " + request.getURI() : "";
348+
String requestInfo = (logger.isDebugEnabled() ? request.getMethod() + " " + request.getURI() : null);
352349
try {
353350
if (sockJsPath.equals("") || sockJsPath.equals("/")) {
354-
logger.debug(requestInfo);
351+
if (requestInfo != null) {
352+
logger.debug("Processing transport request: " + requestInfo);
353+
}
355354
response.getHeaders().setContentType(new MediaType("text", "plain", UTF8_CHARSET));
356355
response.getBody().write("Welcome to SockJS!\n".getBytes(UTF8_CHARSET));
357356
}
358357
else if (sockJsPath.equals("/info")) {
359-
logger.debug(requestInfo);
358+
if (requestInfo != null) {
359+
logger.debug("Processing transport request: " + requestInfo);
360+
}
360361
this.infoHandler.handle(request, response);
361362
}
362363
else if (sockJsPath.matches("/iframe[0-9-.a-z_]*.html")) {
363364
if (!this.allowedOrigins.isEmpty() && !this.allowedOrigins.contains("*")) {
364-
if (logger.isDebugEnabled()) {
365-
logger.debug("Iframe support is disabled when an origin check is required, ignoring " +
366-
requestInfo);
365+
if (requestInfo != null) {
366+
logger.debug("Iframe support is disabled when an origin check is required. " +
367+
"Ignoring transport request: " + requestInfo);
367368
}
368369
response.setStatusCode(HttpStatus.NOT_FOUND);
369370
return;
370371
}
371372
if (this.allowedOrigins.isEmpty()) {
372373
response.getHeaders().add(XFRAME_OPTIONS_HEADER, "SAMEORIGIN");
373374
}
374-
logger.debug(requestInfo);
375+
if (requestInfo != null) {
376+
logger.debug("Processing transport request: " + requestInfo);
377+
}
375378
this.iframeHandler.handle(request, response);
376379
}
377380
else if (sockJsPath.equals("/websocket")) {
378381
if (isWebSocketEnabled()) {
379-
logger.debug(requestInfo);
382+
if (requestInfo != null) {
383+
logger.debug("Processing transport request: " + requestInfo);
384+
}
380385
handleRawWebSocketRequest(request, response, wsHandler);
381386
}
382-
else if (logger.isDebugEnabled()) {
383-
logger.debug("WebSocket disabled, ignoring " + requestInfo);
387+
else if (requestInfo != null) {
388+
logger.debug("WebSocket disabled. Ignoring transport request: " + requestInfo);
384389
}
385390
}
386391
else {
387392
String[] pathSegments = StringUtils.tokenizeToStringArray(sockJsPath.substring(1), "/");
388393
if (pathSegments.length != 3) {
389394
if (logger.isWarnEnabled()) {
390-
logger.warn("Ignoring invalid transport request " + requestInfo);
395+
logger.warn("Invalid SockJS path '" + sockJsPath + "' - required to have 3 path segments");
396+
}
397+
if (requestInfo != null) {
398+
logger.debug("Ignoring transport request: " + requestInfo);
391399
}
392400
response.setStatusCode(HttpStatus.NOT_FOUND);
393401
return;
394402
}
403+
395404
String serverId = pathSegments[0];
396405
String sessionId = pathSegments[1];
397406
String transport = pathSegments[2];
398407

399408
if (!isWebSocketEnabled() && transport.equals("websocket")) {
400-
if (logger.isDebugEnabled()) {
401-
logger.debug("WebSocket transport is disabled, ignoring " + requestInfo);
409+
if (requestInfo != null) {
410+
logger.debug("WebSocket disabled. Ignoring transport request: " + requestInfo);
402411
}
403412
response.setStatusCode(HttpStatus.NOT_FOUND);
404413
return;
405414
}
406415
else if (!validateRequest(serverId, sessionId, transport)) {
407-
if (logger.isWarnEnabled()) {
408-
logger.warn("Ignoring transport request " + requestInfo);
416+
if (requestInfo != null) {
417+
logger.debug("Ignoring transport request: " + requestInfo);
409418
}
410419
response.setStatusCode(HttpStatus.NOT_FOUND);
411420
return;
412421
}
422+
423+
if (requestInfo != null) {
424+
logger.debug("Processing transport request: " + requestInfo);
425+
}
413426
handleTransportRequest(request, response, wsHandler, sessionId, transport);
414427
}
415428
response.close();
@@ -421,14 +434,16 @@ else if (!validateRequest(serverId, sessionId, transport)) {
421434

422435
protected boolean validateRequest(String serverId, String sessionId, String transport) {
423436
if (!StringUtils.hasText(serverId) || !StringUtils.hasText(sessionId) || !StringUtils.hasText(transport)) {
424-
logger.warn("No server, session, or transport path segment");
437+
logger.warn("No server, session, or transport path segment in SockJS request.");
425438
return false;
426439
}
440+
427441
// Server and session id's must not contain "."
428442
if (serverId.contains(".") || sessionId.contains(".")) {
429443
logger.warn("Either server or session contains a \".\" which is not allowed by SockJS protocol.");
430444
return false;
431445
}
446+
432447
return true;
433448
}
434449

@@ -445,6 +460,7 @@ protected abstract void handleRawWebSocketRequest(ServerHttpRequest request,
445460
protected abstract void handleTransportRequest(ServerHttpRequest request, ServerHttpResponse response,
446461
WebSocketHandler webSocketHandler, String sessionId, String transport) throws SockJsException;
447462

463+
448464
protected boolean checkOrigin(ServerHttpRequest request, ServerHttpResponse response,
449465
HttpMethod... httpMethods) throws IOException {
450466

@@ -454,7 +470,9 @@ protected boolean checkOrigin(ServerHttpRequest request, ServerHttpResponse resp
454470

455471
if (!WebUtils.isValidOrigin(request, this.allowedOrigins)) {
456472
String origin = request.getHeaders().getOrigin();
457-
logger.debug("Request rejected, Origin header value " + origin + " not allowed");
473+
if (logger.isWarnEnabled()) {
474+
logger.warn("Origin header value '" + origin + "' not allowed.");
475+
}
458476
response.setStatusCode(HttpStatus.FORBIDDEN);
459477
return false;
460478
}

Diff for: spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/SockJsHttpRequestHandler.java

+10-10
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
package org.springframework.web.socket.sockjs.support;
1818

1919
import java.io.IOException;
20-
2120
import javax.servlet.ServletContext;
2221
import javax.servlet.ServletException;
2322
import javax.servlet.http.HttpServletRequest;
@@ -66,8 +65,8 @@ public class SockJsHttpRequestHandler
6665
* @param webSocketHandler the websocket handler
6766
*/
6867
public SockJsHttpRequestHandler(SockJsService sockJsService, WebSocketHandler webSocketHandler) {
69-
Assert.notNull(sockJsService, "sockJsService must not be null");
70-
Assert.notNull(webSocketHandler, "webSocketHandler must not be null");
68+
Assert.notNull(sockJsService, "SockJsService must not be null");
69+
Assert.notNull(webSocketHandler, "WebSocketHandler must not be null");
7170
this.sockJsService = sockJsService;
7271
this.webSocketHandler =
7372
new ExceptionWebSocketHandlerDecorator(new LoggingWebSocketHandlerDecorator(webSocketHandler));
@@ -95,10 +94,6 @@ public void setServletContext(ServletContext servletContext) {
9594
}
9695
}
9796

98-
@Override
99-
public boolean isRunning() {
100-
return this.running;
101-
}
10297

10398
@Override
10499
public void start() {
@@ -120,6 +115,11 @@ public void stop() {
120115
}
121116
}
122117

118+
@Override
119+
public boolean isRunning() {
120+
return this.running;
121+
}
122+
123123

124124
@Override
125125
public void handleRequest(HttpServletRequest servletRequest, HttpServletResponse servletResponse)
@@ -139,13 +139,13 @@ public void handleRequest(HttpServletRequest servletRequest, HttpServletResponse
139139
private String getSockJsPath(HttpServletRequest servletRequest) {
140140
String attribute = HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE;
141141
String path = (String) servletRequest.getAttribute(attribute);
142-
return ((path.length() > 0) && (path.charAt(0) != '/')) ? "/" + path : path;
142+
return (path.length() > 0 && path.charAt(0) != '/' ? "/" + path : path);
143143
}
144144

145145
@Override
146146
public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
147-
if (sockJsService instanceof CorsConfigurationSource) {
148-
return ((CorsConfigurationSource)sockJsService).getCorsConfiguration(request);
147+
if (this.sockJsService instanceof CorsConfigurationSource) {
148+
return ((CorsConfigurationSource) this.sockJsService).getCorsConfiguration(request);
149149
}
150150
return null;
151151
}

Diff for: spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/TransportHandlingSockJsService.java

+19-11
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,7 @@
6060
* @author Sebastien Deleuze
6161
* @since 4.0
6262
*/
63-
public class TransportHandlingSockJsService extends AbstractSockJsService
64-
implements SockJsServiceConfig, Lifecycle {
63+
public class TransportHandlingSockJsService extends AbstractSockJsService implements SockJsServiceConfig, Lifecycle {
6564

6665
private static final boolean jackson2Present = ClassUtils.isPresent(
6766
"com.fasterxml.jackson.databind.ObjectMapper", TransportHandlingSockJsService.class.getClassLoader());
@@ -154,10 +153,6 @@ public List<HandshakeInterceptor> getHandshakeInterceptors() {
154153
return this.interceptors;
155154
}
156155

157-
@Override
158-
public boolean isRunning() {
159-
return this.running;
160-
}
161156

162157
@Override
163158
public void start() {
@@ -183,6 +178,11 @@ public void stop() {
183178
}
184179
}
185180

181+
@Override
182+
public boolean isRunning() {
183+
return this.running;
184+
}
185+
186186

187187
@Override
188188
protected void handleRawWebSocketRequest(ServerHttpRequest request, ServerHttpResponse response,
@@ -322,13 +322,21 @@ else if (transportType.supportsCors()) {
322322

323323
@Override
324324
protected boolean validateRequest(String serverId, String sessionId, String transport) {
325-
if (!getAllowedOrigins().contains("*") && !TransportType.fromValue(transport).supportsOrigin()) {
326-
if (logger.isWarnEnabled()) {
327-
logger.warn("Origin check has been enabled, but transport " + transport + " does not support it");
328-
}
325+
if (!super.validateRequest(serverId, sessionId, transport)) {
329326
return false;
330327
}
331-
return super.validateRequest(serverId, sessionId, transport);
328+
329+
if (!getAllowedOrigins().contains("*")) {
330+
TransportType transportType = TransportType.fromValue(transport);
331+
if (transportType == null || !transportType.supportsOrigin()) {
332+
if (logger.isWarnEnabled()) {
333+
logger.warn("Origin check enabled but transport '" + transport + "' does not support it.");
334+
}
335+
return false;
336+
}
337+
}
338+
339+
return true;
332340
}
333341

334342
private SockJsSession createSockJsSession(String sessionId, SockJsSessionFactory sessionFactory,

Diff for: spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/TransportType.java

+17-17
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,8 @@ public enum TransportType {
5050
HTML_FILE("htmlfile", HttpMethod.GET, "cors", "jsessionid", "no_cache");
5151

5252

53-
private final String value;
54-
55-
private final HttpMethod httpMethod;
56-
57-
private final List<String> headerHints;
58-
5953
private static final Map<String, TransportType> TRANSPORT_TYPES;
54+
6055
static {
6156
Map<String, TransportType> transportTypes = new HashMap<String, TransportType>();
6257
for (TransportType type : values()) {
@@ -65,8 +60,19 @@ public enum TransportType {
6560
TRANSPORT_TYPES = Collections.unmodifiableMap(transportTypes);
6661
}
6762

63+
public static TransportType fromValue(String value) {
64+
return TRANSPORT_TYPES.get(value);
65+
}
66+
67+
68+
private final String value;
69+
70+
private final HttpMethod httpMethod;
6871

69-
private TransportType(String value, HttpMethod httpMethod, String... headerHints) {
72+
private final List<String> headerHints;
73+
74+
75+
TransportType(String value, HttpMethod httpMethod, String... headerHints) {
7076
this.value = value;
7177
this.httpMethod = httpMethod;
7278
this.headerHints = Arrays.asList(headerHints);
@@ -77,9 +83,6 @@ public String value() {
7783
return this.value;
7884
}
7985

80-
/**
81-
* The HTTP method for this transport.
82-
*/
8386
public HttpMethod getHttpMethod() {
8487
return this.httpMethod;
8588
}
@@ -88,6 +91,10 @@ public boolean sendsNoCacheInstruction() {
8891
return this.headerHints.contains("no_cache");
8992
}
9093

94+
public boolean sendsSessionCookie() {
95+
return this.headerHints.contains("jsessionid");
96+
}
97+
9198
public boolean supportsCors() {
9299
return this.headerHints.contains("cors");
93100
}
@@ -96,13 +103,6 @@ public boolean supportsOrigin() {
96103
return this.headerHints.contains("cors") || this.headerHints.contains("origin");
97104
}
98105

99-
public boolean sendsSessionCookie() {
100-
return this.headerHints.contains("jsessionid");
101-
}
102-
103-
public static TransportType fromValue(String value) {
104-
return TRANSPORT_TYPES.get(value);
105-
}
106106

107107
@Override
108108
public String toString() {

0 commit comments

Comments
 (0)