Spring - 集成测试 - 无法连接到安全的 Websocket

Sun*_*ezz 6 java spring unit-testing session-cookies spring-websocket

我有一个带有 Spring-Security/Session 和 Spring-Websocket 的 Spring Boot 2.2 MVC 应用程序。
它被配置为仅在经过身份验证时允许 websocket 连接。

以下是在单元测试中创建 Websocket 客户端的推荐方法。(至少我在 spring 文档中看到过这个)

private StompSession createWebsocket() throws InterruptedException, ExecutionException, TimeoutException {

    List<Transport> transports = new ArrayList<>(1);
    transports.add(new WebSocketTransport(new StandardWebSocketClient()));
    WebSocketStompClient stompClient = new WebSocketStompClient(new SockJsClient(transports));
    stompClient.setMessageConverter(new MappingJackson2MessageConverter());

    StompSession stompSession = stompClient.connect("ws://localhost:" + port + "/app",  new  StompSessionHandlerAdapter() {}).get(1, SECONDS);
    return stompSession;
}

Run Code Online (Sandbox Code Playgroud)

问题

但是,这样我在日志中得到了异常,因为 Spring 重定向到登录页面,这通常是好的和需要的,因为 Websocket 请求未经身份验证。
日志确认:


2019-11-12 18:03:32.487 DEBUG 19592 --- [o-auto-1-exec-3] o.s.s.w.u.m.AndRequestMatcher            : All requestMatchers returned true
2019-11-12 18:03:32.487 DEBUG 19592 --- [o-auto-1-exec-3] o.s.s.w.DefaultRedirectStrategy          : Redirecting to 'http://localhost:51599/login'
...
2019-11-12 18:03:32.513 DEBUG 19592 --- [o-auto-1-exec-4] s.s.w.c.SecurityContextPersistenceFilter : SecurityContextHolder now cleared, as request processing completed
2019-11-12 18:03:32.514 ERROR 19592 --- [cTaskExecutor-1] o.s.w.s.s.c.DefaultTransportRequest      : No more fallback transports after TransportRequest[url=ws://localhost:51599/app/299/6928331c14b94beba4315294661970c3/websocket]
javax.websocket.DeploymentException: The HTTP response from the server [200] did not permit the HTTP upgrade to WebSocket
    at org.apache.tomcat.websocket.WsWebSocketContainer.connectToServerRecursive(WsWebSocketContainer.java:437) ~[tomcat-embed-websocket-9.0.27.jar:9.0.27]
    at org.apache.tomcat.websocket.WsWebSocketContainer.connectToServerRecursive(WsWebSocketContainer.java:395) ~[tomcat-embed-websocket-9.0.27.jar:9.0.27]
    at org.apache.tomcat.websocket.WsWebSocketContainer.connectToServer(WsWebSocketContainer.java:197) ~[tomcat-embed-websocket-9.0.27.jar:9.0.27]
    at org.springframework.web.socket.client.standard.StandardWebSocketClient.lambda$doHandshakeInternal$0(StandardWebSocketClient.java:151) ~[spring-websocket-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:264) [?:?]
    at java.util.concurrent.FutureTask.run(FutureTask.java) [?:?]
    at java.lang.Thread.run(Thread.java:844) [?:?]

Run Code Online (Sandbox Code Playgroud)

虽然没有任何地方记录如何验证 websocket 客户端,除了会话头之外,我尝试手动登录,检索会话 cookie 以将其设置在 WS 连接/握手头中,但没有运气,因为 MockMvc 似乎没有存储或在标题中设置任​​何 cookie。


2019-11-12 18:03:32.487 DEBUG 19592 --- [o-auto-1-exec-3] o.s.s.w.u.m.AndRequestMatcher            : All requestMatchers returned true
2019-11-12 18:03:32.487 DEBUG 19592 --- [o-auto-1-exec-3] o.s.s.w.DefaultRedirectStrategy          : Redirecting to 'http://localhost:51599/login'
...
2019-11-12 18:03:32.513 DEBUG 19592 --- [o-auto-1-exec-4] s.s.w.c.SecurityContextPersistenceFilter : SecurityContextHolder now cleared, as request processing completed
2019-11-12 18:03:32.514 ERROR 19592 --- [cTaskExecutor-1] o.s.w.s.s.c.DefaultTransportRequest      : No more fallback transports after TransportRequest[url=ws://localhost:51599/app/299/6928331c14b94beba4315294661970c3/websocket]
javax.websocket.DeploymentException: The HTTP response from the server [200] did not permit the HTTP upgrade to WebSocket
    at org.apache.tomcat.websocket.WsWebSocketContainer.connectToServerRecursive(WsWebSocketContainer.java:437) ~[tomcat-embed-websocket-9.0.27.jar:9.0.27]
    at org.apache.tomcat.websocket.WsWebSocketContainer.connectToServerRecursive(WsWebSocketContainer.java:395) ~[tomcat-embed-websocket-9.0.27.jar:9.0.27]
    at org.apache.tomcat.websocket.WsWebSocketContainer.connectToServer(WsWebSocketContainer.java:197) ~[tomcat-embed-websocket-9.0.27.jar:9.0.27]
    at org.springframework.web.socket.client.standard.StandardWebSocketClient.lambda$doHandshakeInternal$0(StandardWebSocketClient.java:151) ~[spring-websocket-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:264) [?:?]
    at java.util.concurrent.FutureTask.run(FutureTask.java) [?:?]
    at java.lang.Thread.run(Thread.java:844) [?:?]

Run Code Online (Sandbox Code Playgroud)

请求打印的输出:


MockHttpServletRequest:
      HTTP Method = POST
      Request URI = /perform_login
       Parameters = {email=[user@test.com], pw=[test123], _csrf=[933cd6ad-0c81-4539-82a5-b184babcad84]}
          Headers = [Accept:"application/x-www-form-urlencoded"]
             Body = <no character encoding set>
    Session Attrs = {userid=88, SPRING_SECURITY_CONTEXT=org.springframework.security.core.context.SecurityContextImpl@c08a487f: Authentication: org.springframework.security.authentication.UsernamePasswordAuthenticationToken@c08a487f: Principal: com.package.UserDetails@7ca802e3; Credentials: [PROTECTED]; Authenticated: true; Details: null; Granted Authorities: FULL_AUTH, username=user@test.com}

Handler:
             Type = null

Async:
    Async started = false
     Async result = null

Resolved Exception:
             Type = null

ModelAndView:
        View name = null
             View = null
            Model = null

FlashMap:
       Attributes = null

MockHttpServletResponse:
           Status = 200
    Error message = null
          Headers = [X-Redirect:"/", Content-Type:"application/json", X-Content-Type-Options:"nosniff", X-XSS-Protection:"1; mode=block", Cache-Control:"no-cache, no-store, max-age=0, must-revalidate", Pragma:"no-cache", Expires:"0", X-Frame-Options:"DENY"]
     Content type = application/json
             Body = {"code":200, "status":"ok"}
    Forwarded URL = null
   Redirected URL = null
          Cookies = []

Run Code Online (Sandbox Code Playgroud)

应用程序就像一个魅力,登录正在工作,websocket 也在工作。
正常的登录以 cookie 响应,一切正常。

问题

  1. 如何以优雅的(弹簧)方式在 JUnit 测试场景中使用给定用户验证 websocket 客户端?
  2. 如何从 MockMVC 结果中获取会话 ID 以成功连接 websocket(通过在握手/升级标头中插入 cookie)?
  3. 有没有其他方法测试安全的 websocket?

已知的相关 SO 主题:

与 Websocket 连接相同的异常(但没有答案)
与 MockMVC-Cookies 的行为相同(没有一个答案有效)