You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Ktor框架下WebSockets路由使用JWT进行身份认证的实现问题

Ktor WebSocket 场景 JWT 认证实现方案

核心原理说明

常规HTTP场景的JWT认证默认从Authorization请求头提取token,而WebSocket握手阶段本质是HTTP GET请求,仅需要调整token提取逻辑即可复用原有JWT校验能力,不需要完全重写认证逻辑。

两种可落地实现方案

方案1:握手阶段认证(推荐,性能最优)

在WebSocket握手请求阶段完成JWT校验,校验不通过直接拒绝连接,无需额外建立连接消耗。

实现步骤:

  • 自定义JWT认证器的token提取规则,同时兼容URL参数和请求头两种token传递方式,适配不同客户端场景
install(Authentication) {
    jwt("websocket-jwt") {
        realm = "your-service-realm"
        verifier(yourPreConfiguredJwtVerifier) // 复用HTTP场景已配置的JWT校验器即可
        extractCredentials { call ->
            // 优先取URL参数中的token,适配原生浏览器WebSocket场景
            call.request.queryParameters["token"]?.let {
                return@extractCredentials JWTCredential(it)
            }
            // 兼容支持自定义头的客户端(如APP、后端服务)从请求头传token
            call.request.headers["Authorization"]?.removePrefix("Bearer ")?.let {
                return@extractCredentials JWTCredential(it)
            }
            null
        }
        validate { credential ->
            // 直接复用HTTP场景的用户校验逻辑即可
            val userId = credential.payload.getClaim("user_id").asLong()
            userService.getUserById(userId)?.let {
                UserIdPrincipal(userId.toString())
            }
        }
    }
}
  • 路由配置时将WebSocket接口放入认证作用域即可
routing {
    authenticate("websocket-jwt") {
        webSocket("/api/v1/websocket/connect") {
            // 进入该路由时已经完成认证,可直接获取用户身份
            val currentUserId = call.principal<UserIdPrincipal>()?.name ?: run {
                close(CloseReason(CloseReason.Codes.INTERNAL_ERROR, "Auth failed"))
                return@webSocket
            }
            // 后续正常WebSocket业务逻辑
        }
    }
}

注意事项:

URL参数可能会被服务器日志、代理日志记录,对安全性要求极高的场景可以选择方案2。

方案2:连接建立后首帧认证

连接建立后要求客户端第一帧必须发送JWT token,校验不通过直接关闭连接,避免token出现在URL中。

webSocket("/api/v1/websocket/connect") {
    // 读取第一帧作为token,3秒超时未发送直接关闭
    val token = withTimeoutOrNull(3000) { receiveDeserialized<String>() } ?: run {
        close(CloseReason(CloseReason.Codes.VIOLATED_POLICY, "No token provided"))
        return@webSocket
    }
    // 校验token合法性,复用HTTP场景校验逻辑
    val payload = try {
        yourPreConfiguredJwtVerifier.verify(token).payload
    } catch (e: JWTVerificationException) {
        close(CloseReason(CloseReason.Codes.VIOLATED_POLICY, "Invalid token"))
        return@webSocket
    }
    // 校验用户合法性
    val currentUser = userService.getUserById(payload.getClaim("user_id").asLong()) ?: run {
        close(CloseReason(CloseReason.Codes.VIOLATED_POLICY, "User not exist"))
        return@webSocket
    }
    // 后续正常WebSocket业务逻辑
}

常见认知误区修正

  • 原生浏览器WebSocket API不支持自定义请求头,不要强制要求客户端通过Authorization头传递token,否则前端无法实现
  • 不需要对每一条WebSocket消息都做JWT校验,要么在握手阶段完成,要么在首帧完成,避免不必要的性能消耗
  • 不要跳过身份认证直接放行WebSocket连接,未认证的连接会带来严重的安全风险

内容的提问来源于stack exchange,提问作者Miso Piso

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.28 14:45:04