使用由 jose4j 生成的令牌在 node.js 中使用 jsonwebtoken 验证 JWT 失败

Posted

技术标签:

【中文标题】使用由 jose4j 生成的令牌在 node.js 中使用 jsonwebtoken 验证 JWT 失败【英文标题】:Verifying JWT using jsonwebtoken in node.js with a token generated by jose4j fails 【发布时间】:2015-12-21 01:01:25 【问题描述】:

我正在尝试使用 node.js 中的 jsonwebtoken 验证 jose4j 生成的 json Web 令牌,我看到以下错误:

[错误:PEM_read_bio_PUBKEY 失败]

jose4j 代码基本上是直接从示例中提取出来的:

RsaJsonWebKey key = RsaJwkGenerator.generateJwk(2048);
key.setKeyId("global.authenticated");

byte[] raw = key.getKey().getEncoded();
Base64.Encoder encoder = Base64.getEncoder();
System.out.printf("Public Key [%s]\n", encoder.encodeToString(raw));

JwtClaims claims = new JwtClaims();
claims.setIssuer("global.gen");
claims.setAudience("global.cons");
claims.setExpirationTimeMinutesInTheFuture(12 * 60);
claims.setGeneratedJwtId();
claims.setIssuedAtToNow();
claims.setNotBeforeMinutesInThePast(2);
claims.setSubject("nim");
claims.setClaim("role", "tester");

JsonWebSignature jws = new JsonWebSignature();
jws.setPayload(claims.toJson());
jws.setKey(key.getPrivateKey());
jws.setKeyIdHeaderValue(key.getKeyId());
jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA256);
String token = jws.getCompactSerialization();
System.out.printf("Generated Token [%s]\n", token);


JwtConsumer jwtConsumer = new JwtConsumerBuilder()
    .setRequireExpirationTime() // the JWT must have an expiration time
    .setAllowedClockSkewInSeconds(30) // allow some leeway in validating time based claims to account for clock skew
    .setRequireSubject() // the JWT must have a subject claim
    .setExpectedIssuer("global.gen") // whom the JWT needs to have been issued by
    .setExpectedAudience("global.cons") // to whom the JWT is intended for
    .setVerificationKey(key.getKey()) // verify the signature with the public key
    .build(); // create the JwtConsumer instance

try 
  //  Validate the JWT and process it to the Claims
  JwtClaims jwtClaims = jwtConsumer.processToClaims(token);
  System.out.println("JWT validation succeeded! " + jwtClaims);
 catch (InvalidJwtException e) 
  // InvalidJwtException will be thrown, if the JWT failed processing or validation in anyway.
  // Hopefully with meaningful explanations(s) about what went wrong.
  System.out.println("Invalid JWT! " + e);

因此,令牌在内部验证良好。但是,当我复制令牌和密钥时(例如下面是来自运行),就会报告上述错误:

var jwt        = require('jsonwebtoken'); // used to create, sign, and verify tokens

var key = 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkRWAQ0O9LgBoHNAB5m1X8e1sPTzKmBTPCFTSRTzw0AjZozIbN4nIp/3jnQHTbcY0Bf5MDWmtdheSK1a+ew34YcgN2b9Shr+3yZv9PJ97i7gRCqOnI7jbm7PXFBNw1I4aMYc6tV7TKFzvx6008/nYvN3Jey6Z8ItS/FLQRDkV9m/WQkhJpYgvmD6qiwj9d+un+moBQ5/PPgn7Qkg5GyxZUy9PsblUDSrIA0bEiv/wQOXCYUvL9OFzxTUSeIHpdGibhPQVxX3Jnpr293Iq/mOKn3ZO+xBID26m3L8+ik64wte041y1S4HHaE9Q082ai/uBduAwIHcJY5VAHborZYCSaQIDAQAB';
var token = 'eyJraWQiOiJnbG9iYWwuYXV0aGVudGljYXRlZCIsImFsZyI6IlJTMjU2In0.eyJpc3MiOiJnbG9iYWwuZ2VuIiwiYXVkIjoiZ2xvYmFsLmNvbnMiLCJleHAiOjE0NDMwNjMyMDgsImp0aSI6InpweF9ERW8tX1h2Q1hnZmNZTUpiZ0EiLCJpYXQiOjE0NDMwMjAwMDgsIm5iZiI6MTQ0MzAxOTg4OCwic3ViIjoibmltIiwicm9sZSI6InRlc3RlciJ9.inEebSQ8jYPQsTpHnvw-gMpoNbJl5ErUkS8FtkDagWrwijUgG8XYYP8FLi2ZCpdgDqUsP6nE1iG0_2wWuL7B7C7wUpZlrqR2bEOG2cXK9s26VqNAXu8I7BTDaZBKmdOt1aFVWozGsN8iUCsQ7Yt9-GfvNRP1yeOoMgpOxf_wVa0QVzsV18aVi_oSeiMqOkQ_6n7JOjFVdiURm0ew4vh5TBaMcEcS35a9jtPxuFR_Z_FaLUk0g06PDVKcdsK1-FYRAGBlRGDkea8Hs9Zh-ZIxgcs2QfWzq5PSsIKum1dWqNLW04ullWmlbAO-5d0V0NAnkh4FFoi3N7AedvkILJgbqA';

jwt.verify(token, key,  algorithms: ['RS256'] , function(err, decoded) 
  if (err)
    console.log(err);
  else
    console.log(decoded);
);

我是否缺少一些魔法(在 jose4j 或 jsonwebtoken 中)可以让生成的令牌通过公钥进行验证?

附带说明,将令牌粘贴到 jwt.io 可以正确解码标头和有效负载,但是无法使用相同的公钥验证签名。我猜问题确实出在 jose4j 方面 - 但不确定。

【问题讨论】:

【参考方案1】:

如果有人遇到类似问题,我会将其添加为答案。问题源于传递给 jsonwebtoken 的密钥的格式。它不能只是纯文本公钥,它必须专门遵循 PEM 格式。因此,一旦我将密钥转换为 PEM 文件,特别是通过进行以下更改:

byte[] raw = key.getKey().getEncoded();
Base64.Encoder encoder = Base64.getMimeEncoder(64, new byte[]'\n');

然后用包装

-----BEGIN PUBLIC KEY-----
<Key>
-----END PUBLIC KEY-----

然后将生成的文件传递到 jsonwebtoken 允许身份验证继续进行。我没有意识到传递到验证步骤的证书应该被如此严格地格式化(即使用行大小和包装器!)

注意:bouncycastle 包有一个PemObject 类和一个PemWriter,这应该会使编写文件更容易一些——但是我不想仅仅为此而引入另一个包。也许 jose4j 的维护者可以为他们的包添加一个小类..

【讨论】:

我维护 jose4j,虽然 PEM 格式确实超出了库的范围,但我会考虑为特定的事情添加一些简单的实用程序,例如获取 PEM 编码的公钥。鼓励 auth0 的人支持 JWK 也是很好的,这是一种很好的基于 JSON 的密钥编码方式。 谢谢!我只是把它放进去跟踪 PEM 编码功能bitbucket.org/b_c/jose4j/issues/37 下一个版本将有一个KeyPairUtil.pemEncode(PublicKey publicKey) 辅助方法来执行此操作。也解码。 jose4j v0.5.0 最近发布,具有KeyPairUtil.pemEncode(PublicKey publicKey)fromPemEncoded(String pem),这将有助于将PublicKey 对象与PEM 编码密钥相互转换。【参考方案2】:

似乎jsonwebtoken 不仅对-----BEGIN PUBLIC KEY----- 页眉和页脚非常严格,而且对换行符也非常严格。

像这样的键

MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkRWAQ0O9LgBoHNAB5m1X8e1sPTzKmBTPCFTSRTzw0AjZozIbN4nIp/3jnQHTbcY0Bf5MDWmtdheSK1a+ew34YcgN2b9Shr+3yZv9PJ97i7gRCqOnI7jbm7PXFBNw1I4aMYc6tV7TKFzvx6008/nYvN3Jey6Z8ItS/FLQRDkV9m/WQkhJpYgvmD6qiwj9d+un+moBQ5/PPgn7Qkg5GyxZUy9PsblUDSrIA0bEiv/wQOXCYUvL9OFzxTUSeIHpdGibhPQVxX3Jnpr293Iq/mOKn3ZO+xBID26m3L8+ik64wte041y1S4HHaE9Q082ai/uBduAwIHcJY5VAHborZYCSaQIDAQAB

必须看起来像这样才能与jsonwebtoken 一起使用(注意页眉、页脚和换行符):

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkRWAQ0O9LgBoHNAB5m1X
8e1sPTzKmBTPCFTSRTzw0AjZozIbN4nIp/3jnQHTbcY0Bf5MDWmtdheSK1a+ew34
YcgN2b9Shr+3yZv9PJ97i7gRCqOnI7jbm7PXFBNw1I4aMYc6tV7TKFzvx6008/nY
vN3Jey6Z8ItS/FLQRDkV9m/WQkhJpYgvmD6qiwj9d+un+moBQ5/PPgn7Qkg5GyxZ
Uy9PsblUDSrIA0bEiv/wQOXCYUvL9OFzxTUSeIHpdGibhPQVxX3Jnpr293Iq/mOK
n3ZO+xBID26m3L8+ik64wte041y1S4HHaE9Q082ai/uBduAwIHcJY5VAHborZYCS
aQIDAQAB
-----END PUBLIC KEY-----

这个功能为我完成了这项工作:

function base64toPem(base64)

    for(var result="", lines=0;result.length-lines < base64.length;lines++) 
        result+=base64.substr(result.length-lines,64)+"\n"
    

    return "-----BEGIN PUBLIC KEY-----\n" + result + "-----END PUBLIC KEY-----";

【讨论】:

以上是关于使用由 jose4j 生成的令牌在 node.js 中使用 jsonwebtoken 验证 JWT 失败的主要内容,如果未能解决你的问题,请参考以下文章

Json Web 令牌 - jose4j - SyntaxError:JSON 中位置 0 的意外令牌 e

Node.js 中未使用 RS256 算法生成 jsonwebtoken 令牌

我应该如何使用 Google Cloud KMS 存储由另一个应用程序生成的访问令牌?

如何解码 Java 生成的 Node.js 中的 jwt 令牌?

Node.js:登录后在 URL 中发送带有令牌(JWT)的新请求

如何在 Node.js 中正确实现“忘记/重置密码”功能? (使用一次性令牌)