向现有 Spring 应用程序添加 Web 套接字支持
Posted
技术标签:
【中文标题】向现有 Spring 应用程序添加 Web 套接字支持【英文标题】:Adding web socket support to an existing Spring application 【发布时间】:2016-07-27 11:10:17 【问题描述】:我正在使用Spring 4
应用程序(不幸的是,不是Spring Boot
),我无法让网络套接字工作。我在/ws
路径注册了一个简单的TextWebSocketHandler
实现,并且我确实得到了确认日志:
将 URL 路径 [/ws] 映射到类型的处理程序 [班级 org.springframework.web.socket.server.support.WebSocketHttpRequestHandler]
...但我无法连接到网络浏览器中的地址。我已经尝试过这种基于注释的配置:
@Slf4j
@Configuration
@EnableWebSocket
public class WebSocketConfiguration implements WebSocketConfigurer
public static final String WEB_SOCKET_PATH = "/ws";
@Autowired
private WebSocketService handler;
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry)
log.info("Registering web socket handler at ''.", handler, WEB_SOCKET_PATH);
registry.addHandler(handler, WEB_SOCKET_PATH);
我可以确认WebSocketService handler
不为空并且实际上实现了WebSocketHandler
接口。虽然配置器明显被触发,但与"ws://localhost:8081/ws"
连接会导致错误:
到 'ws://localhost:8081/ws' 的 WebSocket 连接失败:期间出错 WebSocket 握手:意外响应代码:404
错误目标:WebSocket,isTrusted:true,currentTarget:WebSocket, eventPhase:2,气泡:false,cancelable:false,defaultPrevented: 错误,时间戳:1469616712980247,原始目标:WebSocket, 显式原始目标:WebSocket,无:0
我用来测试网络套接字机制的客户端代码非常简单:
var ws = new WebSocket("ws://localhost:8081/ws");
ws.onerror = function(e) console.log(e);
ws.onopen = function() console.log("Opened");
我什至尝试通过 XML 配置注册处理程序,但是虽然我得到了几乎相同的日志,但它仍然无法正常工作。请注意,我正在使用 Jetty 9 的几乎默认配置运行应用程序(通过 Gretty Gradle 插件)。
查看 Spring 调试日志时,我只能猜测 Spring 无法将 HTTP 请求提升到 Web 套接字连接:
13:14:02.322 [qtp1305935114-14] DEBUG - /ws at position 1 of 13 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
13:14:02.322 [qtp1305935114-14] DEBUG - Obtained a valid SecurityContext from SPRING_SECURITY_CONTEXT: 'org.springframework.security.core.context.SecurityContextImpl@4190548c: Authentication: org.springframework.security.authentication.UsernamePasswordAuthenticationToken@4190548c: Principal: org.springframework.security.core.userdetails.User@58c7c40: Username: [removed]; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_USER; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@12afc: RemoteIpAddress: 127.0.0.1; SessionId: 1om9yxo93cwce1qb1tu0rcs15n; Granted Authorities: ROLE_USER'
13:14:02.322 [qtp1305935114-14] DEBUG - /ws at position 2 of 13 in additional filter chain; firing Filter: 'ConcurrentSessionFilter'
13:14:02.322 [qtp1305935114-14] DEBUG - /ws at position 3 of 13 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
13:14:02.322 [qtp1305935114-14] DEBUG - /ws at position 4 of 13 in additional filter chain; firing Filter: 'HeaderWriterFilter'
13:14:02.322 [qtp1305935114-14] DEBUG - /ws at position 5 of 13 in additional filter chain; firing Filter: 'LogoutFilter'
13:14:02.322 [qtp1305935114-14] DEBUG - Checking match of request : '/ws'; against '/logout'
13:14:02.322 [qtp1305935114-14] DEBUG - /ws at position 6 of 13 in additional filter chain; firing Filter: 'UsernamePasswordAuthenticationFilter'
13:14:02.322 [qtp1305935114-14] DEBUG - Request 'GET /ws' doesn't match 'POST /securityCheck
13:14:02.322 [qtp1305935114-14] DEBUG - /ws at position 7 of 13 in additional filter chain; firing Filter: 'BasicAuthenticationFilter'
13:14:02.322 [qtp1305935114-14] DEBUG - /ws at position 8 of 13 in additional filter chain; firing Filter: 'RequestCacheAwareFilter'
13:14:02.322 [qtp1305935114-14] DEBUG - /ws at position 9 of 13 in additional filter chain; firing Filter: 'SecurityContextHolderAwareRequestFilter'
13:14:02.322 [qtp1305935114-14] DEBUG - /ws at position 10 of 13 in additional filter chain; firing Filter: 'AnonymousAuthenticationFilter'
13:14:02.322 [qtp1305935114-14] DEBUG - SecurityContextHolder not populated with anonymous token, as it already contained: 'org.springframework.security.authentication.UsernamePasswordAuthenticationToken@4190548c: Principal: org.springframework.security.core.userdetails.User@58c7c40: Username: [removed]; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_USER; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@12afc: RemoteIpAddress: 127.0.0.1; SessionId: 1om9yxo93cwce1qb1tu0rcs15n; Granted Authorities: ROLE_USER'
13:14:02.322 [qtp1305935114-14] DEBUG - /ws at position 11 of 13 in additional filter chain; firing Filter: 'SessionManagementFilter'
13:14:02.322 [qtp1305935114-14] DEBUG - /ws at position 12 of 13 in additional filter chain; firing Filter: 'ExceptionTranslationFilter'
13:14:02.322 [qtp1305935114-14] DEBUG - /ws at position 13 of 13 in additional filter chain; firing Filter: 'FilterSecurityInterceptor'
13:14:02.322 [qtp1305935114-14] DEBUG - Checking match of request : '/ws'; against '/login*'
13:14:02.322 [qtp1305935114-14] DEBUG - Secure object: FilterInvocation: URL: /ws; Attributes: [ROLE_USER]
13:14:02.322 [qtp1305935114-14] DEBUG - Previously Authenticated: org.springframework.security.authentication.UsernamePasswordAuthenticationToken@4190548c: Principal: org.springframework.security.core.userdetails.User@58c7c40: Username: [removed]; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_USER; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@12afc: RemoteIpAddress: 127.0.0.1; SessionId: 1om9yxo93cwce1qb1tu0rcs15n; Granted Authorities: ROLE_USER
13:14:02.322 [qtp1305935114-14] DEBUG - Voter: org.springframework.security.access.vote.RoleVoter@b5de58f, returned: 1
13:14:02.322 [qtp1305935114-14] DEBUG - Authorization successful
13:14:02.322 [qtp1305935114-14] DEBUG - RunAsManager did not change Authentication object
13:14:02.322 [qtp1305935114-14] DEBUG - /ws reached end of additional filter chain; proceeding with original chain
13:14:02.323 [qtp1305935114-14] DEBUG - Not injecting HSTS header since it did not match the requestMatcher org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher@425f619f
13:14:02.324 [qtp1305935114-14] DEBUG - Chain processed normally
13:14:02.324 [qtp1305935114-14] DEBUG - SecurityContextHolder now cleared, as request processing completed
查看官方文档和示例后,似乎 web sockets 配置非常标准。是否有任何我可能遗漏的内容或我可能需要更改的任何配置才能在 Spring 中启用 Web 套接字?
【问题讨论】:
【参考方案1】:事实证明 Web 套接字按预期工作,但 Spring servlet 映射未设置为 web.xml
中的 /*
:
<servlet-mapping>
<servlet-name>spring</servlet-name>
<url-pattern>/spring/*</url-pattern>
</servlet-mapping>
这很愚蠢,真的 - 我所要做的就是将地址更改为ws://localhost:8081/spring/ws
。让我措手不及的是,一些 Spring 过滤器被映射到 /*
,我的印象是 Spring servlet 共享了这个映射——毕竟,对 ws://localhost:8081/ws
的调用触发了一些 Spring 的东西(如日志中所示) .
【讨论】:
【参考方案2】:我认为你应该在 WebSocketConfigurer 类中添加 @Controller 注释,如果不是所有的 http 请求都将由 Spring 的 DispatcherServlet 处理。相关:Spring websocket getting 404 not found
【讨论】:
我已经看到了最相关的问题,并且列出的解决方案都没有对我有用,真的。我的@Configuration
-annotated 组件被找到并被触发,Web 套接字处理程序似乎已正确注册,但我仍然在/ws
得到 404。似乎它从未尝试将 HTTP 请求提升为 Web 套接字会话。以上是关于向现有 Spring 应用程序添加 Web 套接字支持的主要内容,如果未能解决你的问题,请参考以下文章
将 Spring Security 添加到现有 Spring Web App(使用 JavaConfig)
将 Spring Security 添加到现有 Spring AngularJS 应用程序
Spring Data ElasticSearch(7.9.3)将字段添加到现有索引
为啥向现有行添加新行时,链接/联结表行的主 ID 会发生变化?
从 Spring Boot Web 应用程序中的 Web 套接字获取数据
Heroku上的现有Ruby on Rails Web应用程序(PostgreSQL),Devise身份验证,需要为移动支持添加Rails API