WebSocket鉴权 前后端配置

1、 前端修改:连接时携带Token

在前端建立WebSocket连接时,将Token作为查询参数附加到URL中。

const token = localStorage.getItem('auth_token'); 
//示例地址
const ws_url = `ws://127.18.177.208:8080/websocket/${getRandomString(8)}?token=${encodeURIComponent(token)}`;

2、 创建鉴权配置类(处理Spring依赖)

import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

@Component
public class SpringContext implements ApplicationContextAware {
    private static ApplicationContext context;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        context = applicationContext;
    }

    public static <T> T getBean(Class<T> beanClass) {
        return context.getBean(beanClass);
    }
}

3、创建自定义鉴权配置器

import javax.websocket.HandshakeResponse;
import javax.websocket.server.HandshakeRequest;
import javax.websocket.server.ServerEndpointConfig;
import java.util.List;
import java.util.Map;

public class WebSocketAuthConfigurator extends ServerEndpointConfig.Configurator {
    @Override
    public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
        // 从请求参数获取Token
        Map<String, List<String>> params = request.getParameterMap();
        List<String> tokens = params.get("token");
        String token = (tokens != null && !tokens.isEmpty()) ? tokens.get(0) : null;

        // 从Spring容器获取JWT工具类
        JwtUtil jwtUtil = SpringContext.getBean(JwtUtil.class);
        
        if (token == null || !jwtUtil.validateToken(token)) {
            throw new RuntimeException("连接未授权,Token验证失败");
        }
    }
}

4、 修改WebSocketServer类配置

@ServerEndpoint(value = "/websocket/{sid}", configurator = WebSocketAuthConfigurator.class)
public class WebSocketServer {
    // 原有代码保持不变...
}

5、网关配置调整(移除白名单)

6、JWT 工具类

如果 auth 模块含有 token 权限验证方法可直接替换

以下为示例代码

1、引入依赖

  <dependency>
      <groupId>io.jsonwebtoken</groupId>
      <artifactId>jjwt-api</artifactId>
      <version>0.11.5</version>
  </dependency>
  <dependency>
      <groupId>io.jsonwebtoken</groupId>
      <artifactId>jjwt-impl</artifactId>
      <version>0.11.5</version>
      <scope>runtime</scope>
  </dependency>
  <dependency>
      <groupId>io.jsonwebtoken</groupId>
      <artifactId>jjwt-jackson</artifactId>
      <version>0.11.5</version>
      <scope>runtime</scope>
  </dependency>

2、JWT 示例代码

import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.crypto.SecretKey;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;


@Component
public class JwtUtil {

    // 从配置文件中读取密钥和有效期
    @Value("${jwt.secret}")
    private String secret;

    @Value("${jwt.expiration}")
    private Long expiration;

    // 生成安全密钥
    private SecretKey getSecretKey() {
        return Keys.hmacShaKeyFor(secret.getBytes());
    }

    /**
     * 生成JWT Token
     * @param claims 自定义声明(建议至少包含用户标识)
     * @return Token字符串
     */
    public String generateToken(Map<String, Object> claims) {
        return Jwts.builder()
                .setClaims(claims)                      // 自定义声明
                .setIssuedAt(new Date())                // 签发时间
                .setExpiration(new Date(System.currentTimeMillis() + expiration)) // 过期时间
                .signWith(getSecretKey(), SignatureAlgorithm.HS256) // 签名算法
                .compact();
    }

    /**
     * 验证Token有效性
     * @param token 需要验证的Token
     * @return 是否有效
     */
    public boolean validateToken(String token) {
        try {
            Jwts.parserBuilder()
                    .setSigningKey(getSecretKey())
                    .build()
                    .parseClaimsJws(token);
            return true;
        } catch (SecurityException e) {
            log.error("Invalid JWT signature: {}", e.getMessage());
        } catch (MalformedJwtException e) {
            log.error("Invalid JWT token: {}", e.getMessage());
        } catch (ExpiredJwtException e) {
            log.error("JWT token is expired: {}", e.getMessage());
        } catch (UnsupportedJwtException e) {
            log.error("JWT token is unsupported: {}", e.getMessage());
        } catch (IllegalArgumentException e) {
            log.error("JWT claims string is empty: {}", e.getMessage());
        }
        return false;
    }

    /**
     * 解析Token获取声明信息
     * @param token Token字符串
     * @return 声明集合
     */
    public Claims parseToken(String token) {
        return Jwts.parserBuilder()
                .setSigningKey(getSecretKey())
                .build()
                .parseClaimsJws(token)
                .getBody();
    }

    /**
     * 获取指定声明
     * @param token Token字符串
     * @param claimKey 声明键名
     * @return 声明值
     */
    public <T> T getClaim(String token, String claimKey, Class<T> requiredType) {
        final Claims claims = parseToken(token);
        return claims.get(claimKey, requiredType);
    }

    /**
     * 生成包含用户ID的Token(示例方法)
     * @param userId 用户ID
     * @return Token字符串
     */
    public String generateTokenWithUserId(String userId) {
        Map<String, Object> claims = new HashMap<>();
        claims.put("userId", userId);
        return generateToken(claims);
    }

    /**
     * 从Token中获取用户ID(示例方法)
     * @param token Token字符串
     * @return 用户ID
     */
    public String getUserIdFromToken(String token) {
        return getClaim(token, "userId", String.class);
    }
}

3、在 application.propertiesapplication.yml 中添加配置

# JWT配置
jwt.secret=your-256-bit-secret-key-base64-encoded-here-1234567890
jwt.expiration=86400000  # 24小时(单位:毫秒)
posted @ 2025-04-21 10:58  溪山晚风  阅读(81)  评论(0)    收藏  举报