电话咨询
This commit is contained in:
@@ -298,9 +298,10 @@ func (s *AuthService) Logout(ctx context.Context, userID int64, clientType strin
|
||||
}
|
||||
|
||||
// ValidateAccessToken 校验 Access Token 是否在 Redis 中有效
|
||||
// 供 JWT 中间件调用,实现有状态 JWT 验证,按 clientType 隔离校验
|
||||
func (s *AuthService) ValidateAccessToken(ctx context.Context, userID int64, clientType, token string) bool {
|
||||
return s.tokenStore.ValidateAccessToken(ctx, userID, clientType, token)
|
||||
// 供 JWT 中间件 / WS 握手调用,实现有状态 JWT 验证,按 clientType 隔离校验;
|
||||
// username 用于识别云律临时会议账号并放宽「单点登录挤号」校验
|
||||
func (s *AuthService) ValidateAccessToken(ctx context.Context, userID int64, clientType, token, username string) bool {
|
||||
return s.tokenStore.ValidateAccessToken(ctx, userID, clientType, token, username)
|
||||
}
|
||||
|
||||
// GetProfile 获取用户个人信息
|
||||
|
||||
@@ -3,6 +3,7 @@ package service
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/echochat/backend/config"
|
||||
@@ -17,8 +18,18 @@ import (
|
||||
const (
|
||||
keyPrefixAccessToken = "echo:auth:token:" // echo:auth:token:{client_type}:{user_id}
|
||||
keyPrefixRefreshToken = "echo:auth:refresh:" // echo:auth:refresh:{client_type}:{user_id}
|
||||
|
||||
// cloudLawUsernamePrefix 云律临时会议账号用户名前缀(与 meeting.cloudLawUsername 保持一致)。
|
||||
// 这类账号按手机号即时创建、仅用于视频/语音会议,会在「双方进会 / SSO 多跳 / 多端」时被重复签发,
|
||||
// 单点登录语义会把先签发的 token 挤掉。对其放宽校验,避免 web-view 进会误报「认证已失效」。
|
||||
cloudLawUsernamePrefix = "cloudlaw_"
|
||||
)
|
||||
|
||||
// isCloudLawUsername 判断是否为云律临时会议账号
|
||||
func isCloudLawUsername(username string) bool {
|
||||
return strings.HasPrefix(username, cloudLawUsernamePrefix)
|
||||
}
|
||||
|
||||
// TokenStore 管理 Token 在 Redis 中的存取
|
||||
// 实现有状态 JWT:登录存入、验证时校验、登出时删除
|
||||
type TokenStore struct {
|
||||
@@ -70,7 +81,8 @@ func (s *TokenStore) SaveTokens(ctx context.Context, userID int64, clientType, a
|
||||
|
||||
// ValidateAccessToken 校验 Access Token 是否与 Redis 中存储的一致
|
||||
// clientType 用于定位正确的 Redis key(前台 vs 管理端)
|
||||
func (s *TokenStore) ValidateAccessToken(ctx context.Context, userID int64, clientType, token string) bool {
|
||||
// username 用于识别云律临时会议账号,对其放宽「单点登录挤号」校验
|
||||
func (s *TokenStore) ValidateAccessToken(ctx context.Context, userID int64, clientType, token, username string) bool {
|
||||
funcName := "service.token_store.ValidateAccessToken"
|
||||
|
||||
key := fmt.Sprintf("%s%s:%d", keyPrefixAccessToken, clientType, userID)
|
||||
@@ -91,7 +103,23 @@ func (s *TokenStore) ValidateAccessToken(ctx context.Context, userID int64, clie
|
||||
return false
|
||||
}
|
||||
|
||||
return stored == token
|
||||
if stored == token {
|
||||
return true
|
||||
}
|
||||
|
||||
// 云律临时会议账号:同一手机号在「双方进会 / SSO 多跳 / 多端」场景会被重复签发会话,
|
||||
// 单点登录会把先签发的 token 挤掉,导致 web-view 进会报「认证已失效」。
|
||||
// 这里只要该账号在 Redis 中仍存在有效会话(stored 非空,未登出/未过期),
|
||||
// 且 token 已通过 JWT 签名与有效期校验(调用方 ParseToken 已保证),即放行。
|
||||
if stored != "" && isCloudLawUsername(username) {
|
||||
logs.Debug(ctx, funcName, "云律临时账号 token 已被新会话覆盖,按宽松策略放行",
|
||||
zap.Int64("user_id", userID),
|
||||
zap.String("client_type", clientType),
|
||||
)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// ValidateRefreshToken 校验 Refresh Token 是否与 Redis 中存储的一致
|
||||
|
||||
@@ -20,7 +20,7 @@ import (
|
||||
// TokenValidator 有状态 JWT 验证接口(检查 Token 是否在 Redis 中有效)
|
||||
// 由 auth.AuthService 实现,用于防止已登出用户建立 WebSocket 连接
|
||||
type TokenValidator interface {
|
||||
ValidateAccessToken(ctx context.Context, userID int64, clientType, token string) bool
|
||||
ValidateAccessToken(ctx context.Context, userID int64, clientType, token, username string) bool
|
||||
}
|
||||
|
||||
// OfflineMessagePusher 离线消息推送接口
|
||||
@@ -160,7 +160,7 @@ func (h *Handler) Upgrade(c *gin.Context) {
|
||||
if clientType == "" {
|
||||
clientType = "frontend"
|
||||
}
|
||||
if h.tokenValidator != nil && !h.tokenValidator.ValidateAccessToken(c.Request.Context(), claims.UserID, clientType, token) {
|
||||
if h.tokenValidator != nil && !h.tokenValidator.ValidateAccessToken(c.Request.Context(), claims.UserID, clientType, token, claims.Username) {
|
||||
logs.Warn(nil, funcName, "WebSocket Token 已失效(Redis 校验)",
|
||||
zap.Int64("user_id", claims.UserID))
|
||||
utils.ResponseUnauthorized(c, "认证已失效,请重新登录")
|
||||
|
||||
Reference in New Issue
Block a user