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

Spring Cloud APIGateway JWT认证签名不匹配错误求助

Spring Cloud JWT认证签名不匹配问题

问题场景

基于jjwt最新版本实现Spring Cloud JWT认证,认证成功后将Token写入响应头,但API网关校验时抛出以下异常:

io.jsonwebtoken.security.SignatureException: JWT签名与本地计算的签名不匹配。无法确认JWT的有效性,不应信任该JWT。

相关代码如下:

API Gateway 校验代码(AuthorizationHeaderFilter.java)

private boolean isJwtValid(String jwt) {
        boolean isValid = false;
        String subject = null;
        String tokenSecret = env.getProperty("token.secret");
        SecretKey signingKey = Keys.hmacShaKeyFor(tokenSecret.getBytes());

        JwtParser parser = Jwts.parser().verifyWith(signingKey).build();
        
        try {
            Jwt<Header, Claims> parsedToken = (Jwt<Header, Claims>) parser.parseUnsecuredClaims(jwt);
            subject = parsedToken.getPayload().getSubject();
        }
        catch(Exception ex) {
            System.out.println("ERRRRRRRRROR");
        }
        
        if(subject !=null && !subject.isEmpty()) {
            isValid = true;
        }
        return isValid;
    }

用户服务Token生成代码(AuthenticationFilter.java)

@Override
    protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse res, FilterChain chain,
            Authentication auth) throws IOException, ServletException {
        String userName = ((User) auth.getPrincipal()).getUsername();
        UserDto userDto = userService.getUserDetailsByEmail(userName);
        String tokenSecret = environment.getProperty("token.secret");
        byte[] secretKeyBytes = Base64.encodeBase64(tokenSecret.getBytes());
        SecretKey secretKey = Keys.hmacShaKeyFor(secretKeyBytes);
        
        Instant now = Instant.now();
        String token = Jwts.builder().subject(userDto.getUserId()).expiration(
                Date.from(now.plusMillis(Long.parseLong(environment.getProperty("token.expiration_time")))))
        .issuedAt(Date.from(now))
        .signWith(secretKey)
        .compact();
        
        res.addHeader("token", token);
        res.addHeader("userId", userDto.getUserId());
    }

问题根源

Token生成与校验环节的密钥处理逻辑不一致:

  • 用户服务生成Token时,对token.secret做了Base64编码后再生成签名密钥
  • API网关校验时,直接使用token.secret的原始字节生成校验密钥

两者密钥处理逻辑不统一,导致签名校验失败。此外,网关侧使用parseUnsecuredClaims方法会跳过签名校验,仅解析Payload,即使签名错误也会返回有效,存在安全隐患。

修复方案

1. 统一密钥处理逻辑(二选一)

方案A:修改用户服务,移除Base64编码

删除用户服务中对密钥的Base64编码步骤,直接使用原始密钥字节生成密钥:

// 原代码
// byte[] secretKeyBytes = Base64.encodeBase64(tokenSecret.getBytes());
// 修改后
byte[] secretKeyBytes = tokenSecret.getBytes();
SecretKey secretKey = Keys.hmacShaKeyFor(secretKeyBytes);

方案B:修改API网关,添加Base64解码

如果需要保留Base64编码的密钥处理,网关侧需对原始密钥进行Base64解码:

// 原代码
// SecretKey signingKey = Keys.hmacShaKeyFor(tokenSecret.getBytes());
// 修改后
byte[] decodedSecret = Base64.decodeBase64(tokenSecret.getBytes());
SecretKey signingKey = Keys.hmacShaKeyFor(decodedSecret);

2. 修复网关签名校验逻辑

将网关侧的parseUnsecuredClaims替换为parseSignedClaims,确保签名校验生效:

// 原代码
// Jwt<Header, Claims> parsedToken = (Jwt<Header, Claims>) parser.parseUnsecuredClaims(jwt);
// 修改后
Jwt<Header, Claims> parsedToken = parser.parseSignedClaims(jwt);

内容的提问来源于stack exchange,提问作者Robin Singh

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 13:52:48