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中存储的是明文密码,会导致登录验证失败。解决方法:
- 运行以下代码生成BCrypt加密后的密码:
public static void main(String[] args) { System.out.println(new BCryptPasswordEncoder().encode("aymen")); System.out.println(new BCryptPasswordEncoder().encode("messikh")); }
- 将生成的加密字符串替换到
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注入:
然后在JwtUtils中注入:jwt.secret=671491AE98362741F722202EED3288E8FF2508B35315ADBF75EEB3195A926B40@Value("${jwt.secret}") private String secret;
内容的提问来源于stack exchange,提问作者Aymen Messikh
相关产品推荐
相关产品推荐

