密钥字节只能用于HMAC签名。请指定一个PublicKey或PrivateKey实例。

13

我正在尝试读取由Google OpenID Connect的ID Token生成的JSON Web Token(JWT),以获取声明并使用jjwt库进行验证。 我已尝试了几种方法来使用下面的代码解决问题。


 String publicKeyFromJsonFile = "-----BEGIN PUBLIC KEY-----xxxxxxx-----END PUBLIC KEY-----"

 Claims claims = Jwts.parser()
                .setSigningKey(publicKeyFromJsonFile)
                .parseClaimsJws(jwt).getBody();

 System.out.println(claims);


但我遇到了这个错误:

java.lang.IllegalArgumentException: Key bytes can only be specified for HMAC signatures. Please specify a PublicKey or PrivateKey instance

请问应该采取什么正确的方法?


1
看起来你需要使用KeyFactory解析你的公钥,然后将生成的实例传递给你的库。 - Michał Krzywański
1
嘿@michalk....我最终用这段代码片段修复了publicKeyX509EncodedKeySpec keySpecX509 = new X509EncodedKeySpec(Base64.getDecoder().decode(PUBLIC_KEY));
RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(keySpecX509);
- John Erbynn
它在使用那个代码片段时运行良好。现在出现了不同的错误,即“JWT签名与本地计算的签名不匹配。JWT有效性无法断言,也不应被信任”......我不知道那是否是您实际推荐的。如果不是,您能帮我解决吗?谢谢。 - John Erbynn
4个回答

4

我认为我已经通过解析公钥作为RSAPublicKey来修复了这个问题。以下是我解决此问题的详细过程。

 public static Optional<RSAPublicKey> getParsedPublicKey(){
       // public key content...excluding '---PUBLIC KEY---' and '---END PUBLIC KEY---'
        String PUB_KEY =System.getenv("PUBLIC_KEY") ; 

       // removes white spaces or char 20
        String PUBLIC_KEY = "";
          if (!PUB_KEY.isEmpty()) {
            PUBLIC_KEY = PUB_KEY.replace(" ", "");
        }

        try {
            byte[] decode = com.google.api.client.util.Base64.decodeBase64(PUBLIC_KEY);
            X509EncodedKeySpec keySpecX509 = new X509EncodedKeySpec(decode);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            RSAPublicKey pubKey = (RSAPublicKey) keyFactory.generatePublic(keySpecX509);
            return Optional.of(pubKey);

        } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
            e.printStackTrace();
            System.out.println("Exception block | Public key parsing error ");
            return Optional.empty();
        }

希望这有所帮助 :)。

3
这可以通过以下方式完成,使用私钥签名生成令牌,使用公钥解析声明。
  @Configuration
  public class KeyGeneratorConfig {


    @Value("${jwt.privateKey}")
    private String privateKey; //Encoded private key string


    @Value("${jwt.publicKey}")
    private String publicKey;//Encoded public key string


    @Bean
    public PrivateKey generatePrivateKey() throws NoSuchAlgorithmException, InvalidKeySpecException {

        KeyFactory kf = KeyFactory.getInstance("RSA");
        PKCS8EncodedKeySpec privKeySpecPKCS8 = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey));
        return kf.generatePrivate(privKeySpecPKCS8);
    }

    @Bean
    public PublicKey generatePublicKey() throws NoSuchAlgorithmException, InvalidKeySpecException {

        KeyFactory kf = KeyFactory.getInstance("RSA");
        X509EncodedKeySpec pubKeySpecX509EncodedKeySpec = new X509EncodedKeySpec(Base64.getDecoder().decode(publicKey));
        return kf.generatePublic(pubKeySpecX509EncodedKeySpec);
    }
}

生成令牌和解析可以像这样完成

@Autowired
private PublicKey publicKey;

@Autowired
private PrivateKey privateKey;

private String doGenerateToken(Map claims) {
    return Jwts.builder()
            .setClaims(claims)
            .setExpiration(generateExpirationDate("token"))
            .signWith(SignatureAlgorithm.RS512, privateKey)
            .compact();
}

public Claims getClaimsFromToken(String token) throws ExpiredJwtException, UnsupportedJwtException,
        MalformedJwtException, SignatureException, IllegalArgumentException {
    Claims claims;

    claims = Jwts.parser()
            .setSigningKey(publicKey)
            .parseClaimsJws(token)
            .getBody();

    return claims;
}

1

我通过这篇文章得到了解脱。

import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
...
// First, get your PEM-encoded DER certificate into a String, like this:
String certChart = "-----BEGIN CERTIFICATE-----\n.....";

// Now parse it using CertificateFactory:
CertificateFactory cf = CertificateFactory.getInstance("X.509");
Certificate cert = cf.generateCertificate(new java.io.ByteArrayInputStream(certChars.getBytes(StandardCharsets.US_ASCII)));

// Now verify:
Jwts.parserBuilder()
    .setSigningKey(cert.getPublicKey())
 ...

1
一个好的方法是使用JWT.IO网页手动验证令牌-如我的文章中所述-然后应用等效代码-尽管我的代码是NodeJS。
有兴趣了解您如何手动验证ID令牌- 您可以解释涉及哪些客户端和API-可能有更标准的方法来实现您的目标。

好的。所以我正在使用Angular在客户端使用Google OpenID方法生成tokenID,并将token传递到Java(具体来说是Spring MVC)服务器端进行验证,使用jjwt并在微服务可以访问之前向客户端发送响应,以确保token实际上是有效的。 希望你明白 :) - John Erbynn
好的 - 听起来很标准,除了通常发送访问令牌到API而不是ID令牌,因为访问令牌是短期API凭据,可以更新。否则,您的JWT验证技术方法看起来很好。 - Gary Archer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接