Remove JSONP support

CORS is now widely supported and should be used instead for cross-domain
requests.

Issue: SPR-16914
This commit is contained in:
Sebastien Deleuze
2018-06-07 11:33:25 +02:00
parent 19dc981685
commit ac37b678a3
23 changed files with 25 additions and 713 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2018 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.
@@ -48,7 +48,7 @@ public interface StompWebSocketEndpointRegistration {
* {@code Origin} header value.
*
* <p>When SockJS is enabled and origins are restricted, transport types that do not
* allow to check request origin (JSONP and Iframe based transports) are disabled.
* allow to check request origin (Iframe based transports) are disabled.
* 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 "*"

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2018 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.
@@ -50,7 +50,7 @@ public interface WebSocketHandlerRegistration {
* {@code Origin} header value.
*
* <p>When SockJS is enabled and origins are restricted, transport types that do not
* allow to check request origin (JSONP and Iframe based transports) are disabled.
* allow to check request origin (Iframe based transports) are disabled.
* 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 "*"

View File

@@ -298,9 +298,9 @@ public abstract class AbstractSockJsService implements SockJsService, CorsConfig
* designed for browsers. There is nothing preventing other types of client
* to modify the {@code Origin} header value.
* <p>When SockJS is enabled and origins are restricted, transport types
* that do not allow to check request origin (JSONP and Iframe based
* transports) are disabled. As a consequence, IE 6 to 9 are not supported
* when origins are restricted.
* that do not allow to check request origin (Iframe based transports)
* are disabled. As a consequence, IE 6 to 9 are not supported when origins
* are restricted.
* <p>Each provided allowed origin must have a scheme, and optionally a port
* (e.g. "http://example.org", "http://example.org:9090"). An allowed origin
* string may also be "*" in which case all origins are allowed.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2018 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.
@@ -40,10 +40,6 @@ public enum TransportType {
XHR_SEND("xhr_send", HttpMethod.POST, "cors", "jsessionid", "no_cache"),
JSONP("jsonp", HttpMethod.GET, "jsessionid", "no_cache"),
JSONP_SEND("jsonp_send", HttpMethod.POST, "jsessionid", "no_cache"),
XHR_STREAMING("xhr_streaming", HttpMethod.POST, "cors", "jsessionid", "no_cache"),
EVENT_SOURCE("eventsource", HttpMethod.GET, "origin", "jsessionid", "no_cache"),

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2018 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.
@@ -46,7 +46,7 @@ public abstract class AbstractHttpSendingTransportHandler extends AbstractTransp
implements SockJsSessionFactory {
/**
* Pattern for validating jsonp callback parameter values.
* Pattern for validating callback parameter values.
*/
private static final Pattern CALLBACK_PARAM_PATTERN = Pattern.compile("[0-9A-Za-z_\\.]*");

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2018 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.
@@ -84,8 +84,6 @@ public class DefaultSockJsService extends TransportHandlingSockJsService impleme
result.add(new XhrPollingTransportHandler());
result.add(new XhrReceivingTransportHandler());
result.add(new XhrStreamingTransportHandler());
result.add(new JsonpPollingTransportHandler());
result.add(new JsonpReceivingTransportHandler());
result.add(new EventSourceTransportHandler());
result.add(new HtmlFileTransportHandler());
try {

View File

@@ -1,102 +0,0 @@
/*
* Copyright 2002-2016 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.socket.sockjs.transport.handler;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.util.StringUtils;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.sockjs.SockJsException;
import org.springframework.web.socket.sockjs.SockJsTransportFailureException;
import org.springframework.web.socket.sockjs.frame.DefaultSockJsFrameFormat;
import org.springframework.web.socket.sockjs.frame.SockJsFrameFormat;
import org.springframework.web.socket.sockjs.transport.SockJsSession;
import org.springframework.web.socket.sockjs.transport.TransportType;
import org.springframework.web.socket.sockjs.transport.session.AbstractHttpSockJsSession;
import org.springframework.web.socket.sockjs.transport.session.PollingSockJsSession;
import org.springframework.web.util.JavaScriptUtils;
/**
* A TransportHandler that sends messages via JSONP polling.
*
* @author Rossen Stoyanchev
* @since 4.0
*/
public class JsonpPollingTransportHandler extends AbstractHttpSendingTransportHandler {
@Override
public TransportType getTransportType() {
return TransportType.JSONP;
}
@Override
protected MediaType getContentType() {
return new MediaType("application", "javascript", StandardCharsets.UTF_8);
}
@Override
public boolean checkSessionType(SockJsSession session) {
return session instanceof PollingSockJsSession;
}
@Override
public PollingSockJsSession createSession(
String sessionId, WebSocketHandler handler, Map<String, Object> attributes) {
return new PollingSockJsSession(sessionId, getServiceConfig(), handler, attributes);
}
@Override
public void handleRequestInternal(ServerHttpRequest request, ServerHttpResponse response,
AbstractHttpSockJsSession sockJsSession) throws SockJsException {
try {
String callback = getCallbackParam(request);
if (!StringUtils.hasText(callback)) {
response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
response.getBody().write("\"callback\" parameter required".getBytes(StandardCharsets.UTF_8));
return;
}
}
catch (Throwable ex) {
sockJsSession.tryCloseWithSockJsTransportError(ex, CloseStatus.SERVER_ERROR);
throw new SockJsTransportFailureException("Failed to send error", sockJsSession.getId(), ex);
}
super.handleRequestInternal(request, response, sockJsSession);
}
@Override
protected SockJsFrameFormat getFrameFormat(ServerHttpRequest request) {
// We already validated the parameter above...
String callback = getCallbackParam(request);
return new DefaultSockJsFrameFormat("/**/" + callback + "(\"%s\");\r\n") {
@Override
protected String preProcessContent(String content) {
return JavaScriptUtils.javaScriptEscape(content);
}
};
}
}

View File

@@ -1,85 +0,0 @@
/*
* Copyright 2002-2016 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.socket.sockjs.transport.handler;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.converter.FormHttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.lang.Nullable;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.sockjs.SockJsException;
import org.springframework.web.socket.sockjs.frame.SockJsMessageCodec;
import org.springframework.web.socket.sockjs.transport.TransportHandler;
import org.springframework.web.socket.sockjs.transport.TransportType;
import org.springframework.web.socket.sockjs.transport.session.AbstractHttpSockJsSession;
/**
* A {@link TransportHandler} that receives messages over HTTP.
*
* @author Rossen Stoyanchev
*/
public class JsonpReceivingTransportHandler extends AbstractHttpReceivingTransportHandler {
private final FormHttpMessageConverter formConverter = new FormHttpMessageConverter();
@Override
public TransportType getTransportType() {
return TransportType.JSONP_SEND;
}
@Override
public void handleRequestInternal(ServerHttpRequest request, ServerHttpResponse response,
WebSocketHandler wsHandler, AbstractHttpSockJsSession sockJsSession) throws SockJsException {
super.handleRequestInternal(request, response, wsHandler, sockJsSession);
try {
response.getBody().write("ok".getBytes(StandardCharsets.UTF_8));
}
catch (IOException ex) {
throw new SockJsException("Failed to write to the response body", sockJsSession.getId(), ex);
}
}
@Override
@Nullable
protected String[] readMessages(ServerHttpRequest request) throws IOException {
SockJsMessageCodec messageCodec = getServiceConfig().getMessageCodec();
MediaType contentType = request.getHeaders().getContentType();
if (contentType != null && MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(contentType)) {
MultiValueMap<String, String> map = this.formConverter.read(null, request);
String d = map.getFirst("d");
return (StringUtils.hasText(d) ? messageCodec.decode(d) : null);
}
else {
return messageCodec.decodeInputStream(request.getBody());
}
}
@Override
protected HttpStatus getResponseStatus() {
return HttpStatus.OK;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2018 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.
@@ -55,8 +55,6 @@ import org.springframework.web.socket.sockjs.transport.TransportType;
import org.springframework.web.socket.sockjs.transport.handler.DefaultSockJsService;
import org.springframework.web.socket.sockjs.transport.handler.EventSourceTransportHandler;
import org.springframework.web.socket.sockjs.transport.handler.HtmlFileTransportHandler;
import org.springframework.web.socket.sockjs.transport.handler.JsonpPollingTransportHandler;
import org.springframework.web.socket.sockjs.transport.handler.JsonpReceivingTransportHandler;
import org.springframework.web.socket.sockjs.transport.handler.WebSocketTransportHandler;
import org.springframework.web.socket.sockjs.transport.handler.XhrPollingTransportHandler;
import org.springframework.web.socket.sockjs.transport.handler.XhrReceivingTransportHandler;
@@ -178,8 +176,6 @@ public class HandlersBeanDefinitionParserTests {
containsInAnyOrder(
instanceOf(XhrPollingTransportHandler.class),
instanceOf(XhrReceivingTransportHandler.class),
instanceOf(JsonpPollingTransportHandler.class),
instanceOf(JsonpReceivingTransportHandler.class),
instanceOf(XhrStreamingTransportHandler.class),
instanceOf(EventSourceTransportHandler.class),
instanceOf(HtmlFileTransportHandler.class),

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2018 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.
@@ -30,8 +30,6 @@ public class TransportTypeTests {
assertEquals(TransportType.WEBSOCKET, TransportType.fromValue("websocket"));
assertEquals(TransportType.XHR, TransportType.fromValue("xhr"));
assertEquals(TransportType.XHR_SEND, TransportType.fromValue("xhr_send"));
assertEquals(TransportType.JSONP, TransportType.fromValue("jsonp"));
assertEquals(TransportType.JSONP_SEND, TransportType.fromValue("jsonp_send"));
assertEquals(TransportType.XHR_STREAMING, TransportType.fromValue("xhr_streaming"));
assertEquals(TransportType.EVENT_SOURCE, TransportType.fromValue("eventsource"));
assertEquals(TransportType.HTML_FILE, TransportType.fromValue("htmlfile"));

View File

@@ -63,10 +63,6 @@ public class DefaultSockJsServiceTests extends AbstractHttpRequestTests {
@Mock private TransportHandler xhrSendHandler;
@Mock private SessionCreatingTransportHandler jsonpHandler;
@Mock private TransportHandler jsonpSendHandler;
@Mock private HandshakeTransportHandler wsTransportHandler;
@Mock private WebSocketHandler wsHandler;
@@ -89,9 +85,6 @@ public class DefaultSockJsServiceTests extends AbstractHttpRequestTests {
given(this.xhrHandler.getTransportType()).willReturn(TransportType.XHR);
given(this.xhrHandler.createSession(sessionId, this.wsHandler, attributes)).willReturn(this.session);
given(this.xhrSendHandler.getTransportType()).willReturn(TransportType.XHR_SEND);
given(this.jsonpHandler.getTransportType()).willReturn(TransportType.JSONP);
given(this.jsonpHandler.createSession(sessionId, this.wsHandler, attributes)).willReturn(this.session);
given(this.jsonpSendHandler.getTransportType()).willReturn(TransportType.JSONP_SEND);
given(this.wsTransportHandler.getTransportType()).willReturn(TransportType.WEBSOCKET);
this.service = new TransportHandlingSockJsService(this.taskScheduler, this.xhrHandler, this.xhrSendHandler);
@@ -103,13 +96,11 @@ public class DefaultSockJsServiceTests extends AbstractHttpRequestTests {
DefaultSockJsService service = new DefaultSockJsService(mock(TaskScheduler.class));
Map<TransportType, TransportHandler> handlers = service.getTransportHandlers();
assertEquals(8, handlers.size());
assertEquals(6, handlers.size());
assertNotNull(handlers.get(TransportType.WEBSOCKET));
assertNotNull(handlers.get(TransportType.XHR));
assertNotNull(handlers.get(TransportType.XHR_SEND));
assertNotNull(handlers.get(TransportType.XHR_STREAMING));
assertNotNull(handlers.get(TransportType.JSONP));
assertNotNull(handlers.get(TransportType.JSONP_SEND));
assertNotNull(handlers.get(TransportType.HTML_FILE));
assertNotNull(handlers.get(TransportType.EVENT_SOURCE));
}
@@ -121,7 +112,7 @@ public class DefaultSockJsServiceTests extends AbstractHttpRequestTests {
DefaultSockJsService service = new DefaultSockJsService(mock(TaskScheduler.class), xhrHandler);
Map<TransportType, TransportHandler> handlers = service.getTransportHandlers();
assertEquals(8, handlers.size());
assertEquals(6, handlers.size());
assertSame(xhrHandler, handlers.get(xhrHandler.getTransportType()));
}
@@ -269,28 +260,6 @@ public class DefaultSockJsServiceTests extends AbstractHttpRequestTests {
verifyNoMoreInteractions(this.xhrSendHandler);
}
@Test
public void handleTransportRequestJsonp() throws Exception {
TransportHandlingSockJsService jsonpService = new TransportHandlingSockJsService(
this.taskScheduler, this.jsonpHandler, this.jsonpSendHandler);
String sockJsPath = sessionUrlPrefix+ "jsonp";
setRequest("GET", sockJsPrefix + sockJsPath);
jsonpService.handleRequest(this.request, this.response, sockJsPath, this.wsHandler);
assertEquals(404, this.servletResponse.getStatus());
resetRequestAndResponse();
jsonpService.setAllowedOrigins(Collections.singletonList("http://mydomain1.com"));
setRequest("GET", sockJsPrefix + sockJsPath);
jsonpService.handleRequest(this.request, this.response, sockJsPath, this.wsHandler);
assertEquals(404, this.servletResponse.getStatus());
resetRequestAndResponse();
jsonpService.setAllowedOrigins(Collections.singletonList("*"));
setRequest("GET", sockJsPrefix + sockJsPath);
jsonpService.handleRequest(this.request, this.response, sockJsPath, this.wsHandler);
assertNotEquals(404, this.servletResponse.getStatus());
}
@Test
public void handleTransportRequestWebsocket() throws Exception {
TransportHandlingSockJsService wsService = new TransportHandlingSockJsService(

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2018 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.
@@ -32,7 +32,7 @@ import static org.mockito.BDDMockito.*;
/**
* Test fixture for {@link AbstractHttpReceivingTransportHandler} and sub-classes
* {@link XhrReceivingTransportHandler} and {@link JsonpReceivingTransportHandler}.
* {@link XhrReceivingTransportHandler}.
*
* @author Rossen Stoyanchev
*/
@@ -46,35 +46,6 @@ public class HttpReceivingTransportHandlerTests extends AbstractHttpRequestTests
assertEquals(204, this.servletResponse.getStatus());
}
@Test
public void readMessagesJsonp() throws Exception {
this.servletRequest.setContent("[\"x\"]".getBytes("UTF-8"));
handleRequest(new JsonpReceivingTransportHandler());
assertEquals(200, this.servletResponse.getStatus());
assertEquals("ok", this.servletResponse.getContentAsString());
}
@Test
public void readMessagesJsonpFormEncoded() throws Exception {
this.servletRequest.setContent("d=[\"x\"]".getBytes("UTF-8"));
this.servletRequest.setContentType(MediaType.APPLICATION_FORM_URLENCODED_VALUE);
handleRequest(new JsonpReceivingTransportHandler());
assertEquals(200, this.servletResponse.getStatus());
assertEquals("ok", this.servletResponse.getContentAsString());
}
@Test // SPR-10621
public void readMessagesJsonpFormEncodedWithEncoding() throws Exception {
this.servletRequest.setContent("d=[\"x\"]".getBytes("UTF-8"));
this.servletRequest.setContentType("application/x-www-form-urlencoded;charset=UTF-8");
handleRequest(new JsonpReceivingTransportHandler());
assertEquals(200, this.servletResponse.getStatus());
assertEquals("ok", this.servletResponse.getContentAsString());
}
@Test
public void readMessagesBadContent() throws Exception {
this.servletRequest.setContent("".getBytes("UTF-8"));

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2018 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.
@@ -24,14 +24,11 @@ import org.junit.Test;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.web.socket.AbstractHttpRequestTests;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.sockjs.SockJsTransportFailureException;
import org.springframework.web.socket.sockjs.frame.SockJsFrame;
import org.springframework.web.socket.sockjs.frame.SockJsFrameFormat;
import org.springframework.web.socket.sockjs.transport.session.AbstractSockJsSession;
import org.springframework.web.socket.sockjs.transport.session.PollingSockJsSession;
import org.springframework.web.socket.sockjs.transport.session.StreamingSockJsSession;
import org.springframework.web.socket.sockjs.transport.session.StubSockJsServiceConfig;
import org.springframework.web.util.UriUtils;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
@@ -91,50 +88,6 @@ public class HttpSendingTransportHandlerTests extends AbstractHttpRequestTests
assertEquals("c[2010,\"Another connection still open\"]\n", this.servletResponse.getContentAsString());
}
@Test
public void jsonpTransport() throws Exception {
testJsonpTransport(null, false);
testJsonpTransport("_jp123xYz", true);
testJsonpTransport("A..B__3..4", true);
testJsonpTransport("!jp!abc", false);
testJsonpTransport("<script>", false);
testJsonpTransport("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_.", true);
}
private void testJsonpTransport(String callbackValue, boolean expectSuccess) throws Exception {
JsonpPollingTransportHandler transportHandler = new JsonpPollingTransportHandler();
transportHandler.initialize(this.sockJsConfig);
PollingSockJsSession session = transportHandler.createSession("1", this.webSocketHandler, null);
resetRequestAndResponse();
setRequest("POST", "/");
if (callbackValue != null) {
// need to encode the query parameter
this.servletRequest.setQueryString("c=" + UriUtils.encodeQueryParam(callbackValue, "UTF-8"));
this.servletRequest.addParameter("c", callbackValue);
}
try {
transportHandler.handleRequest(this.request, this.response, this.webSocketHandler, session);
}
catch (SockJsTransportFailureException ex) {
if (expectSuccess) {
throw new AssertionError("Unexpected transport failure", ex);
}
}
if (expectSuccess) {
assertEquals(200, this.servletResponse.getStatus());
assertEquals("application/javascript;charset=UTF-8", this.response.getHeaders().getContentType().toString());
verify(this.webSocketHandler).afterConnectionEstablished(session);
}
else {
assertEquals(500, this.servletResponse.getStatus());
verifyNoMoreInteractions(this.webSocketHandler);
}
}
@Test
public void handleRequestXhrStreaming() throws Exception {
XhrStreamingTransportHandler transportHandler = new XhrStreamingTransportHandler();
@@ -205,10 +158,6 @@ public class HttpSendingTransportHandlerTests extends AbstractHttpRequestTests
format = new EventSourceTransportHandler().getFrameFormat(this.request);
formatted = format.format(frame);
assertEquals("data: " + frame.getContent() + "\r\n\r\n", formatted);
format = new JsonpPollingTransportHandler().getFrameFormat(this.request);
formatted = format.format(frame);
assertEquals("/**/callback(\"" + frame.getContent() + "\");\r\n", formatted);
}
}