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

注册用户遇ECDSA签名密钥错误,JwtService类问题排查

问题描述

注册新用户时碰到两个错误:
先是出现:

ECDSA signing keys must be PrivateKey instances.] with root cause

修改签名算法后又出现:

secret token was not secure enough

问题出在以下JwtService类中:

@Service
public class JwtService {
    //constant secret key that must change
    private static final String PRIVATE_KEY_PATH = "C:\\Users\\ihebt\\Documents\\security\\security\\src\\main\\resources/private.key";
    
    public String extractUserEmail(String jwtToken) {
        return extractClaim(jwtToken, Claims::getSubject);
    }

    public String generateToken(UserDetails userDetails) {
        return generateToken(new HashMap<>(), userDetails);
    }

    public String generateToken(
            Map<String, Object> extraClaims,
            UserDetails userDetails
    ) {
        return Jwts
                .builder()
                .setClaims(extraClaims)
                .setSubject(userDetails.getUsername())
                .setIssuedAt(new Date(System.currentTimeMillis()))
                .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 24))
                .signWith(getSignInKey(), SignatureAlgorithm.ES256)
                .compact();
    }

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

    public boolean isTokenValid(String token, UserDetails userDetails) {
        final String useremail = extractUserEmail(token);
        return (useremail.equals(userDetails.getUsername())) && !isTokenExpired(token);
    }

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

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

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

    private Key getSignInKey() {
        try {
            byte[] privateKeyBytes = Files.readAllBytes(Paths.get(PRIVATE_KEY_PATH));
            return Keys.hmacShaKeyFor(privateKeyBytes);
        } catch (IOException e) {
            throw new RuntimeException("Failed to read private key file", e);
        }
    }
}
问题根源
  1. 第一个错误:代码使用ES256(ECDSA算法)签名,但getSignInKey()方法生成的是HMAC-SHA密钥,ECDSA算法要求必须使用EC私钥,密钥类型不匹配导致报错。
  2. 第二个错误:切换到HMAC类算法后,密钥文件内容长度不足,HS256这类HMAC算法要求密钥至少32字节(256位),长度不够就会触发“secret token不够安全”的提示。
解决办法

办法1:继续使用ECDSA(ES256)算法

修改getSignInKey()方法,正确加载PEM格式的EC私钥:

private Key getSignInKey() {
    try {
        byte[] privateKeyBytes = Files.readAllBytes(Paths.get(PRIVATE_KEY_PATH));
        // 解析PEM格式的EC私钥
        PEMParser pemParser = new PEMParser(new StringReader(new String(privateKeyBytes)));
        JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC");
        PrivateKey privateKey = converter.getPrivateKey((PEMKeyPair) pemParser.readObject());
        pemParser.close();
        return privateKey;
    } catch (IOException e) {
        throw new RuntimeException("Failed to read private key file", e);
    }
}

需要添加BouncyCastle依赖(Spring Boot项目可将以下内容加到pom.xml):

<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcprov-jdk15on</artifactId>
    <version>1.70</version>
</dependency>

同时确保private.key是PEM格式的EC私钥,示例格式如下:

-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIF+...(省略具体内容)...o/5ZAgMBAAE=
-----END EC PRIVATE KEY-----

办法2:改用HMAC(HS256)算法

  1. 修改generateToken()方法中的签名算法:
.signWith(getSignInKey(), SignatureAlgorithm.HS256)
  1. 修改getSignInKey()方法,确保密钥长度达标:
private Key getSignInKey() {
    try {
        byte[] keyBytes = Files.readAllBytes(Paths.get(PRIVATE_KEY_PATH));
        // HS256要求密钥至少32字节
        if (keyBytes.length < 32) {
            throw new IllegalArgumentException("HMAC key must be at least 32 bytes long for HS256");
        }
        return Keys.hmacShaKeyFor(keyBytes);
    } catch (IOException e) {
        throw new RuntimeException("Failed to read secret key file", e);
    }
}

可以用以下命令生成符合要求的密钥文件:

openssl rand -hex 32 > private.key

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 12:36:07