Я работаю над веб-приложением, которое использует Spring Security и WebSockets. Я могу без проблем использовать WebSockets с моей локальной машины, запуская приложение Spring Boot из JAR со встроенным Tomcat. Однако, когда я загружаю тот же JAR/проект в CloudFoundry или OpenShift (и запускаю его как исполняемый JAR), обновление протокола во время установления соединения WebSocket не выполняется.
Я сделал небольшой пример проекта, который демонстрирует эту проблему (по крайней мере, когда я пробую это на своем компьютере или в своей учетной записи CloudFoundry или OpenShift). Он доступен здесь: https://github.com/shakuzen/spring-stomp-websocket-test
Это урезанный, голый пример, но я могу последовательно воссоздать проблему. Сообщение об ошибке в журналах:
2014-10-20T00:46:36.69+0900 [App/0] OUT 2014-10-19 15:46:36.698 DEBUG 32 --- [io-61088-exec-5] o.s.w.s.s.s.DefaultHandshakeHandler : Invalid Upgrade header null
Журналы DEBUG для DefaultHandshakeHandler до этого показывают, что заголовок Upgrade отсутствует. Однако если вы посмотрите на запрос, отправленный с помощью инструментов разработчика Chrome (или эквивалентного инструмента любого браузера), вы увидите, что запрос отличается. Были отправлены следующие два запроса.
1
GET /hello/info HTTP/1.1
Host: sswss-test.cfapps.io
Connection: keep-alive
Authorization: Basic dGVzdHVzZXI6dGVzdHBhc3M=
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.104 Safari/537.36
Accept: */*
Referer: http://sswss-test.cfapps.io/message
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8,ja;q=0.6
Cookie: __VCAP_ID__=693dd6ff1b494f88a2c8567590da500dc44b4818746a45b28dd98a29b2607395; JSESSIONID=C5485065FE0A1DBCDF1F148A63D08FC2
DNT: 1
2
GET ws://sswss-test.cfapps.io/hello/863/olm1kojs/websocket HTTP/1.1
Host: sswss-test.cfapps.io
Connection: Upgrade
Pragma: no-cache
Cache-Control: no-cache
Authorization: Basic dGVzdHVzZXI6dGVzdHBhc3M=
Upgrade: websocket
Origin: http://sswss-test.cfapps.io
Sec-WebSocket-Version: 13
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.104 Safari/537.36
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8,ja;q=0.6
Cookie: __VCAP_ID__=693dd6ff1b494f88a2c8567590da500dc44b4818746a45b28dd98a29b2607395; JSESSIONID=C5485065FE0A1DBCDF1F148A63D08FC2
Sec-WebSocket-Key: 7bW1pg6f9axkVfqV21k/9w==
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
Кажется, он берет заголовки первого запроса и из-за этого терпит неудачу. Однако те же два запроса (конечно, с локальным хостом вместо sswss-test.cf.apps.io и разными значениями для заголовков безопасности) отправляются, когда это выполняется на моем локальном компьютере, и у него нет этой проблемы. Я пробовал это в Chrome и Firefox.
Связанный проект GitHub использует Spring Boot 1.2.0.M2, но я также протестировал последнюю версию выпуска (1.1.8.RELEASE) и получил те же результаты. Я также обнаружил в поиске, что, возможно, SockJS не очень хорошо обрабатывает относительные URL-адреса, поэтому я попытался из консоли запустить команду подключения с абсолютным URL-адресом:
var socket2 = new SockJS('http://sswss-test.cfapps.io/hello');
Но, к сожалению, результат был тот же.
Любые предложения или решения приветствуются. Не стесняйтесь разветвлять проект GitHub и возиться с ним (у OpenShift есть опция бесплатной учетной записи, поэтому вы можете бесплатно развернуть там, чтобы воссоздать проблему). Я скопирую соответствующие части проекта здесь.
WebSocketConfig
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/queue/", "/topic/");
config.setApplicationDestinationPrefixes("/app");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/hello").withSockJS();
}
}
SecurityConfig
Мое фактическое приложение использует Facebook для аутентификации, но я смог воспроизвести проблему с базовой аутентификацией, поэтому я не хотел больше тратить время на дополнительную настройку и усложнение.
@Configuration
@EnableWebMvcSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic()
.and()
//Configures url based authorization
.authorizeRequests()
// Anyone can access the urls
.antMatchers("/").permitAll()
//The rest of the our application is protected.
.antMatchers("/**").authenticated();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("testuser").password("testpass").roles("USER").and()
.withUser("adminuser").password("adminpass").roles("ADMIN","USER");
}
}