我有一个使用Spring Boot 1.0.0.RC5和Tomcat 8.0.3的Web Socket(ws)非安全实现应用程序的工作示例。现在,我想切换到wss,即使用已由Tomcat加载的自签名证书。
这个问题有两个部分,一个是理论上的,一个是实际操作:
理论上的=> 我需要让Tomcat监听两个端口吗?即http和https。我问这个问题是因为我读到在Web Socket通信期间,连接的第一部分是通过http完成的,然后才会进行所谓的“升级”到WebSocket。我将发布我的测试示例。
火狐浏览器提示我"This Connection is Untrusted",然后我告诉火狐浏览器"I understand the risk",并将证书添加为"Security Exception"。从那时起,证书将存储在火狐浏览器的"servers"选项卡下。 到目前为止都很好。但是当我改用wss时,这个游戏不像我期望的那样工作。也就是说,这是在客户端创建套接字的函数。
如果我改变了这行代码
这是完整版本的HTML页面。
补充一点:我还注意到如果我调用它,Web套接字也可以正常工作。
这个问题有两个部分,一个是理论上的,一个是实际操作:
理论上的=> 我需要让Tomcat监听两个端口吗?即http和https。我问这个问题是因为我读到在Web Socket通信期间,连接的第一部分是通过http完成的,然后才会进行所谓的“升级”到WebSocket。我将发布我的测试示例。
GET /hello HTTP/1.1
Host: 127.0.0.5:8080
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:28.0) Gecko/20100101 Firefox/28.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en,en-gb;q=0.5
Accept-Encoding: gzip, deflate
DNT: 1
Sec-WebSocket-Version: 13
Origin: http://localhost:8080
Sec-WebSocket-Key: wIKSOMgaHbEmkaRuEHZ6IA==
Connection: keep-alive, Upgrade
Pragma: no-cache
Cache-Control: no-cache
Upgrade: websocket
HTTP/1.1 101 Switching Protocols
Server: Apache-Coyote/1.1
Upgrade: websocket
Connection: upgrade
Sec-WebSocket-Accept: 8trj6dyzlYdDUA3nAuwiM7SijRM=
Date: Mon, 31 Mar 2014 10:29:19 GMT
..,O.do..*i..nM,..\;..I=.
C!.U.~.U....I....-..Xu.T...H...T.E.d
.'
CONNECTED
heart-beat:0,0
version:1.1
.
.....]..M...F...f9..z?...9..{4..{4..5r...4..h/..{4..|W..
如果使用wss,通信会是什么样子?如果有"upgrade"部分,那么在这种情况下我们需要http才能使其正常工作。
实际上,我面临的问题是负责创建stomp消息的代码部分不起作用,即当我打开页面时。
https://127.0.0.5:8888/wsTest
火狐浏览器提示我"This Connection is Untrusted",然后我告诉火狐浏览器"I understand the risk",并将证书添加为"Security Exception"。从那时起,证书将存储在火狐浏览器的"servers"选项卡下。 到目前为止都很好。但是当我改用wss时,这个游戏不像我期望的那样工作。也就是说,这是在客户端创建套接字的函数。
function connect() {
var socket = new WebSocket("wss://127.0.0.5:8888/hello");
stompClient = Stomp.over(socket);
stompClient.connect({}, function(frame) {
setConnected(true);
console.log('Connected: ' + frame);
stompClient.subscribe('/topic/greetings', function(greeting){
showGreeting(JSON.parse(greeting.body).content);
});
});
}
如果我改变了这行代码
var socket = new WebSocket("wss://127.0.0.5:8888/hello");
转换为http版本,即
var socket = new WebSocket("ws://127.0.0.5:8080/hello");
那么一切就能再次正常工作。问题似乎出在这行代码上:
stompClient.connect({}, function(frame) {
然而根据此错误记录(https://jira.spring.io/browse/SPR-11436),这应该是正确的行。
我使用以下命令生成了证书:
keytool -genkey -alias tomcat -keyalg RSA -keystore /home/tito/Projects/syncServer/Server/certificate/sync.keystore
服务器端:
@Configuration
public class TomcatEmbeded extends SpringServletContainerInitializer {
final int http_port = 8080;
final int https_port = 8888;
final String keystoreFile = "/home/tito/Projects/syncServer/Server/certificate/sync.keystore";
final String keystorePass = "changeit";
final String keystoreType = "JKS";
final String keystoreProvider = "SUN";
final String keystoreAlias = "tomcat";
final String https_scheme = "https";
final String http_scheme = "http";
@Bean
public EmbeddedServletContainerFactory servletContainer() {
TomcatEmbeddedServletContainerFactory factory = new TomcatEmbeddedServletContainerFactory(http_port);
factory.setTomcatContextCustomizers(
Arrays.asList (
new TomcatContextCustomizer[]{
tomcatContextCustomizer()
}
)
);
factory.addConnectorCustomizers( new TomcatConnectorCustomizer() {
@Override
public void customize(Connector con) {
Http11NioProtocol proto = (Http11NioProtocol) con.getProtocolHandler();
try {
con.setPort(https_port);
con.setSecure(true);
con.setScheme("https");
con.setAttribute("keyAlias", keystoreAlias);
con.setAttribute("keystorePass", keystorePass.toString());
try {
con.setAttribute("keystoreFile", ResourceUtils.getFile(keystoreFile).getAbsolutePath());
} catch (FileNotFoundException e) {
throw new IllegalStateException("Cannot load keystore", e);
}
con.setAttribute("clientAuth", "false");
con.setAttribute("sslProtocol", "TLS");
con.setAttribute("SSLEnabled", true);
proto.setSSLEnabled(true);
proto.setKeystoreFile(keystoreFile);
proto.setKeystorePass(keystorePass);
proto.setKeystoreType(keystoreType);
proto.setProperty("keystoreProvider", keystoreProvider.toString());
proto.setKeyAlias(keystoreAlias);
} catch (Exception ex) {
throw new IllegalStateException("can't access keystore: [" + "keystore"
+ "] or truststore: [" + "keystore" + "]", ex);
}
System.out.println("INIT HTTPS");
}
}
);
factory.addAdditionalTomcatConnectors(httpConnector());
// factory.addErrorPages(new ErrorPage(HttpStatus.404, "/notfound.html");
System.out.println("TOMCAT CUSTOME SETTINGS INITILIZED");
return factory;
}
private Connector httpConnector() {
Connector connector = new Connector();
connector.setScheme(this.http_scheme);
connector.setSecure(true);
connector.setPort(this.http_port);
System.out.println("INIT port HTTP");
return connector;
}
@Bean
public TomcatContextCustomizer tomcatContextCustomizer() {
System.out.println("TOMCATCONTEXTCUSTOMIZER INITILIZED");
return new TomcatContextCustomizer() {
@Override
public void customize(Context context) {
// TODO Auto-generated method stub
context.addServletContainerInitializer(new WsSci(), null);
}
};
}
这是Web Socket的配置部分:
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/hello");
}
@Override
public void configureClientInboundChannel(ChannelRegistration channelRegistration) {
}
@Override
public void configureClientOutboundChannel(ChannelRegistration channelRegistration) {
}
@Override
public boolean configureMessageConverters(List<MessageConverter> arg0) {
return true;
}
在Firefox调试控制台上看到的附加消息
Use of getUserData() or setUserData() is deprecated. Use WeakMap or element.dataset instead. requestNotifier.js:64
"Opening Web Socket..." stomp.js:130
Firefox can't establish a connection to the server at wss://127.0.0.5:8888/hello. wsTest:18
"Whoops! Lost connection to wss://127.0.0.5:8888/hello" stomp.js:130
这是完整版本的HTML页面。
<!DOCTYPE html>
<html>
<head>
<title>Hello WebSocket</title>
<script src="/js/stomp.js"></script>
<script type="text/javascript">
var stompClient = null;
function setConnected(connected) {
document.getElementById('connect').disabled = connected;
document.getElementById('disconnect').disabled = !connected;
document.getElementById('conversationDiv').style.visibility = connected ? 'visible' : 'hidden';
document.getElementById('response').innerHTML = '';
}
function connect() {
var socket = new WebSocket("wss://127.0.0.5:8888/hello");
stompClient = Stomp.over(socket);
// stompClient.connect('tito', 'password', function(frame) {
stompClient.connect({}, function(frame) {
setConnected(true);
console.log('Connected: ' + frame);
stompClient.subscribe('/topic/greetings', function(greeting){
showGreeting(JSON.parse(greeting.body).content);
});
});
}
function disconnect() {
stompClient.disconnect();
setConnected(false);
console.log("Disconnected");
}
function sendName() {
var name = document.getElementById('name').value;
stompClient.send("/app/hello", {}, JSON.stringify({ 'name': name }));
}
function showGreeting(message) {
var response = document.getElementById('response');
var p = document.createElement('p');
p.style.wordWrap = 'break-word';
p.appendChild(document.createTextNode(message));
response.appendChild(p);
}
</script>
</head>
<body>
<noscript><h2 style="color: #ff0000">Seems your browser doesn't support Javascript! Websocket relies on Javascript being enabled. Please enable
Javascript and reload this page!</h2></noscript>
<div>
<div>
<button id="connect" onclick="connect();">Connect</button>
<button id="disconnect" disabled="disabled" onclick="disconnect();">Disconnect</button>
</div>
<div id="conversationDiv">
<label>What is your name?</label><input type="text" id="name" />
<button id="sendName" onclick="sendName();">Send</button>
<p id="response"></p>
</div>
</div>
</body>
</html>
使用的stomp脚本版本为 "// Generated by CoffeeScript 1.6.3"
以下是证书生成方式
$ keytool -genkey -alias tomcat -keyalg RSA -keystore /home/tito/Projects/syncServer/Server/certificate/sync.keystore
Enter keystore password:
Re-enter new password:
What is your first and last name?
[Unknown]: TestFirstName
What is the name of your organizational unit?
[Unknown]: TestOrganizationalUnitName
What is the name of your organization?
[Unknown]: TestOrganization
What is the name of your City or Locality?
[Unknown]: TestCity
What is the name of your State or Province?
[Unknown]: TestState
What is the two-letter country code for this unit?
[Unknown]: BG
Is CN=TestFirstName, OU=TestOrganizationalUnitName, O=TestOrganization, L=TestCity, ST=TestState, C=BG correct?
[no]: yes
Enter key password for <tomcat>
(RETURN if same as keystore password):
补充一点:我还注意到如果我调用它,Web套接字也可以正常工作。
https://127.0.0.5:8888/wsTest
但是如果我调用它,它就无法工作。
https://localhost:8888/wsTest
然而我仍然没有找到为什么会发生这种情况。这种行为在Chrome和Firefox中是相同的。