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

Spring Boot中JWT签名不匹配引发403 Forbidden问题求助

问题分析

控制台报错JWT signature does not match locally computed signature,核心原因是JWT生成与解析时使用的签名密钥处理逻辑不一致。你的JwtUtils类中直接将十六进制格式的密钥字符串当作普通UTF-8字符串传入签名方法,导致生成Token和验证Token时的实际密钥不匹配,重启服务后验证逻辑触发签名校验失败,返回403。

修复方案

1. 修正JwtUtils的密钥处理逻辑

启用并修正你注释的getSigningKey方法,确保生成和解析JWT时使用统一的密钥解析方式:

package com.example.demo.Configiration;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

@Component
public class JwtUtils {
    private String secret = "671491AE98362741F722202EED3288E8FF2508B35315ADBF75EEB3195A926B40";

    public String extractUsername(String token) {
        return extractClaim(token, Claims::getSubject);
    }

    public Date extractExpiration(String token) {
        return extractClaim(token, Claims::getExpiration);
    }

    public Boolean hasClaim(String token, String claimName) {
        final Claims claims = extractAllClaims(token);
        return claims.get(claimName) != null;
    }

    public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
        final Claims claims = extractAllClaims(token);
        return claimsResolver.apply(claims);
    }

    private Claims extractAllClaims(String token) {
        return Jwts.parserBuilder()
                .setSigningKey(getSigningKey())
                .build()
                .parseClaimsJws(token)
                .getBody();
    }

    private Boolean isTokenExpired(String token) {
        return extractExpiration(token).before(new Date());
    }

    public String generateToken(UserDetails userDetails) {
        Map<String, Object> claims = new HashMap<>();
        return createToken(claims, userDetails);
    }

    public String generateToken(UserDetails userDetails, Map<String, Object> claims) {
        return createToken(claims, userDetails);
    }

    private String createToken(Map<String, Object> claims, UserDetails userDetails) {
        return Jwts.builder()
                .setClaims(claims)
                .setSubject(userDetails.getUsername())
                .claim("authorities", userDetails.getAuthorities())
                .setIssuedAt(new Date(System.currentTimeMillis()))
                .setExpiration(new Date(System.currentTimeMillis() + TimeUnit.HOURS.toMillis(24)))
                .signWith(getSigningKey(), SignatureAlgorithm.HS256)
                .compact();
    }

    public Boolean isTokenValide(String token, UserDetails userDetails) {
        final String userName = extractUsername(token);
        return (userName.equals(userDetails.getUsername()) && !isTokenExpired(token));
    }

    // 正确解码十六进制格式的密钥
    private Key getSigningKey() {
        byte[] keyBytes = Decoders.HEX.decode(secret);
        return Keys.hmacShaKeyFor(keyBytes);
    }
}

关键修正点:

  • 使用Jwts.parserBuilder()替代过时的Jwts.parser()
  • 生成和解析都调用getSigningKey()获取统一密钥
  • 用Decoders.HEX.decode()解析十六进制密钥字符串

2. 修复密码编码一致性问题

你的AppConfig配置了BCryptPasswordEncoder,但Dto中存储的是明文密码,会导致登录验证失败。解决方法:

  1. 运行以下代码生成BCrypt加密后的密码:
public static void main(String[] args) {
    System.out.println(new BCryptPasswordEncoder().encode("aymen"));
    System.out.println(new BCryptPasswordEncoder().encode("messikh"));
}
  1. 将生成的加密字符串替换到Dto中:
private final static List<UserDetails> APPLICATION_USERS= Arrays.asList(
        new User(
                "aymen@gmail.com",
                "$2a$10$xxxxxx...", // 替换为生成的BCrypt密码
                Collections.singleton(new SimpleGrantedAuthority("ROLE_ADMIN"))
        ),
        new User(
                "messikh@gmail.com",
                "$2a$10xxxxxx...", // 替换为生成的BCrypt密码
                Collections.singleton(new SimpleGrantedAuthority("ROLE_USER"))
        )
);

3. 重新获取Token

之前生成的旧Token是用错误密钥逻辑生成的,修复后旧Token会验证失败,需要重新调用登录接口获取新Token,再用新Token请求受保护接口。

额外优化建议

  • 密钥不要硬编码,建议放到application.properties中通过@Value注入:
    jwt.secret=671491AE98362741F722202EED3288E8FF2508B35315ADBF75EEB3195A926B40
    
    然后在JwtUtils中注入:
    @Value("${jwt.secret}")
    private String secret;
    

内容的提问来源于stack exchange,提问作者Aymen Messikh

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 15:45:54