diff --git a/src/main/java/com/youlai/boot/config/WebSocketConfig.java b/src/main/java/com/youlai/boot/config/WebSocketConfig.java index 1a0e5b2d..6a785a2d 100644 --- a/src/main/java/com/youlai/boot/config/WebSocketConfig.java +++ b/src/main/java/com/youlai/boot/config/WebSocketConfig.java @@ -187,7 +187,13 @@ public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { throw new BadCredentialsException("Token 为空"); } - // 解析并验证 Token + // 校验 Token 有效性(算法+签名+过期时间+黑名单) + if (!tokenManager.validateToken(token)) { + log.warn("⚠ 非法连接请求:Token 无效或已过期"); + throw new BadCredentialsException("Token 无效或已过期"); + } + + // 解析 Token Authentication authentication; try { authentication = tokenManager.parseToken(token); diff --git a/src/main/java/com/youlai/boot/security/token/JwtTokenManager.java b/src/main/java/com/youlai/boot/security/token/JwtTokenManager.java index c3dd612c..c1c0715a 100644 --- a/src/main/java/com/youlai/boot/security/token/JwtTokenManager.java +++ b/src/main/java/com/youlai/boot/security/token/JwtTokenManager.java @@ -49,6 +49,8 @@ import java.util.stream.Collectors; @Service public class JwtTokenManager implements TokenManager { + private static final String ALLOWED_ALGORITHM = "HS256"; + private final SecurityProperties securityProperties; private final RedisTemplate redisTemplate; private final byte[] secretKey; @@ -91,6 +93,14 @@ public class JwtTokenManager implements TokenManager { public Authentication parseToken(String token) { JWT jwt = JWTUtil.parseToken(token); + + if (!isAlgorithmAllowed(jwt)) { + throw new BusinessException(ResultCode.ACCESS_TOKEN_INVALID); + } + if (!jwt.setKey(secretKey).verify()) { + throw new BusinessException(ResultCode.ACCESS_TOKEN_INVALID); + } + JSONObject payloads = jwt.getPayloads(); SysUserDetails userDetails = new SysUserDetails(); userDetails.setUserId(payloads.getLong(JwtClaimConstants.USER_ID)); // 用户ID @@ -164,6 +174,11 @@ public class JwtTokenManager implements TokenManager { */ private boolean validateToken(String token, boolean validateRefreshToken) { JWT jwt = JWTUtil.parseToken(token); + + if (!isAlgorithmAllowed(jwt)) { + return false; + } + // 检查 Token 是否有效(验签 + 是否过期) boolean isValid = jwt.setKey(secretKey).validate(0); @@ -219,6 +234,11 @@ public class JwtTokenManager implements TokenManager { token = token.substring(SecurityConstants.BEARER_TOKEN_PREFIX.length()); } JWT jwt = JWTUtil.parseToken(token); + + if (!isAlgorithmAllowed(jwt) || !jwt.setKey(secretKey).verify()) { + return; + } + JSONObject payloads = jwt.getPayloads(); String jti = payloads.getStr(JWTPayload.JWT_ID); Integer expirationAt = payloads.getInt(JWTPayload.EXPIRES_AT); @@ -400,4 +420,12 @@ public class JwtTokenManager implements TokenManager { return JWTUtil.createToken(payload, secretKey); } + /** + * 校验JWT算法类型是否在允许的白名单中,防止 alg:none 等算法降级攻击 + */ + private boolean isAlgorithmAllowed(JWT jwt) { + Object algorithm = jwt.getHeader("alg"); + return algorithm != null && ALLOWED_ALGORITHM.equals(algorithm.toString()); + } + } diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index 21125ff4..ab14d330 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -87,7 +87,7 @@ security: access-token-time-to-live: 7200 # 访问令牌 有效期(单位:秒),默认 2 小时,-1 表示永不过期 refresh-token-time-to-live: 604800 # 刷新令牌有效期(单位:秒),默认 7 天,-1 表示永不过期 jwt: - secret-key: SecretKey012345678901234567890123456789012345678901234567890123456789 # JWT密钥(HS256算法至少32字符) + secret-key: SecretKey012345678901234567890123456789012345678901234567890123456789 # JWT密钥(HS256算法至少32字符) 【安全警告】生产环境必须更换此默认密钥,否则攻击者可直接伪造合法Token redis-token: allow-multi-login: true # 是否允许多设备登录 # 安全白名单路径,仅跳过 AuthorizationFilter 过滤器,还是会走 Spring Security 的其他过滤器(CSRF、CORS等)