使用 spring 安全性在 Spring 中进行 WebSocket 身份验证

时间:2021-01-25 18:37:18

标签: java spring spring-boot spring-security websocket

根据spring官方文档:

<块引用>

WebSockets 重用了在 建立 WebSocket 连接时的 HTTP 请求。这意味着 HttpServletRequest 上的 Principal 将被移交给 网络套接字。如果您使用的是 Spring Security,则 Principal 上 HttpServletRequest 被自动覆盖。

更具体地说,确保用户已通过您的 WebSocket 身份验证 应用程序,所有需要的是确保您设置 Spring 对基于 HTTP 的 Web 应用程序进行身份验证的安全性。

如果我理解正确,这意味着自握手以来 WebSocket 使用相同的通道进行通信,因此应该在第一次连接时进行身份验证。

然而,没有说明如何以标准的安全方式实际验证握手。据我所知,HTTP 在升级到 WebSockets 时不会发送身份验证标头,那么它是如何完成的?

我是否真的需要在连接查询中发送身份验证令牌,例如

localhost:8080/ws?Auth=... 

并将安全性留给 HTTPS

或者我是否需要在建立连接后对 WebSocket 进行身份验证,例如创建我自己的握手?

有什么合适的正式方法可以做到吗?我正在使用 RAW websockets。

感谢您的想法/帮助。

1 个答案:

答案 0 :(得分:0)

我个人使用 STOMP,但使用 STOMP(基本上是原始 WebSocks 上的通信框架),会话 cookie(来自 Spring Security)与任何消息一起通过套接字发送。

您可以像这样使用 StompAccessorHeader

  @MessageMapping("/agents/start")
    public void start(StompHeaderAccessor stompHeaderAccessor) {
        log.info("Subscriber Start! {}-{}", stompHeaderAccessor.getUser() != null ? stompHeaderAccessor.getUser().getName() : "ANON", stompHeaderAccessor.getSessionId());
        mysessionstore.addSessionId(stompHeaderAccessor.getSessionId());
    }

如果不使用 STOMP 框架,可能有一种方法可以读取原始套接字上每个请求发送的 SessionCookie?

我不是 100% 确定,但我猜您正在使用 TextWebSocketHandler 实现:

@Override
protected void handleTextMessage(WebSocketSession session, TextMessage textMessage)

我可以在 WebSocketSession 的源代码中看到您应该能够在那里获得您的主要身份验证用户:

https://github.com/spring-projects/spring-framework/blob/0de2833894c24c1e70bde991bad171435c6ecac2/spring-websocket/src/main/java/org/springframework/web/socket/WebSocketSession.java#L37

因此,您像 POST "/login" 这样的普通 REST 一样进行身份验证,然后该会话也应该对 websocket 有效。

您可以通过套接字进行身份验证吗?您必须喜欢制作自己的套接字端点以获取他们的凭据并执行 SecurityContextHolder.getContext().setAuthentication(myAuthUserToken) 但也许这会传回会话 cookie?你必须测试这个ofc,因为我不确定它是否会起作用耸肩

然后,我个人创建了一个“存储”(单例或 redis),其中包含用户主体和 socketSessionId,以便我可以将用户与套接字进行匹配。

您可以说将它们存储在带有 HashMap<String,String> userPrincipalNameToSocketSessionId 的单例中,作为一种粗略的方式存储哪个套接字会话属于哪个用户。

例如

有点像

@Override
protected void handleTextMessage(WebSocketSession session, TextMessage textMessage){
  MySessionStore.addSessionToMap(session.getPrincipal(),session.getId());
  log.info("Added user {} websocket session {} to the store.",session.getPrincipal(),session.getId());
}


public */MySingletonClass*/ MySessionStore{

@Getter
public static volatile HashMap<String,String> userPrinciapalToSocketMap = new HashMap<>();

//Method to add to map here
public synchronized static addToMap(String principalName,String webSocketSessionId){
...Adds to the map.
}

JWT 无状态身份验证系统需要套接字会话?

据我所知,这个...除非有很多覆盖和实际上分叉/扩展很多 Spring 类...

你可以制作一个控制器:

Http GET => "/websocket-ticket" 将返回带有用户主体/用户名/id 的签名令牌,用于 UX,然后在 websocket 连接后作为第一条消息传递。

套接字处理程序 TextMessageHandler 可以检查令牌的签名并将其添加到您的 HashMap<String,String> principalUserToSessionId 存储中。

安全问题(不太可能,但确实存在):

具有 XSS 的攻击者可以窥探该令牌并劫持该 websocket 会话。也许你在竞争条件下获胜(即 MITM 需要更长的时间并且令牌是一次性的......更精彩的实现......你现在还需要一个“websocket-ticket-consumed”商店......)。

https://devcenter.heroku.com/articles/websocket-security

我觉得这一切都指向 X/Y 问题。 为什么要使用 JWT 进行身份验证?