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

使用Dart JsonWebToken生成的Client Assertion JWT为何被IDP返回无效签名?

问题:Dart JsonWebToken生成的PS512签名JWT无法通过IDP验证

我用Dart的JsonWebToken库生成OAuth2客户端凭证授权所需的Client Assertion JWT,在JWT.IO上测试签名显示正常,但提交到IDP的client_assertion字段时,返回错误:

"invalid_client" "Invalid token signature"

用相同的密钥、声明和PS512算法,通过Postman中的KJUR库生成的JWT却能正常通过验证。以下是相关代码:

Dart 签名无效的代码示例

Future<String> _generateJWT() async {
  final current_time = DateTime.now();
  final expiration_time = current_time.add(Duration(minutes: 50));
  final issued_time = current_time;
  final uuid = Uuid();
  final jwt = JWT(
    header: Map.from({
      'alg': 'PS512',
      'kid': 'kid1234546',
      'typ': 'JWT',
    }),
    {
      'sub': clientID,
      'aud': 'https://provider.com/oauth/v2.0/token',
      'iss': clientID,
      'exp': (expiration_time.millisecondsSinceEpoch ~/ 1000),
      'iat': (issued_time.millisecondsSinceEpoch ~/ 1000) - 120,
      'jti': uuid.v4(),
    },
  );
  final token = jwt.sign(
    RSAPrivateKey(privateKey),
    algorithm: JWTAlgorithm.PS512,
  );
  return token;
}

Postman/KJUR 可正常运行的代码示例

const clientAssertionJwt = utils.generateSignedRequestJWT(
  KJUR.jws.JWS.sign,
  {
    alg: 'PS512',
    kid,
    typ: 'JWT',
  },
  {
    sub: clientId,
    aud,
    iss: clientId,
    exp: Math.floor(Date.now() / 1000) + 3590,
    iat: Math.floor(Date.now() / 1000) - 120,
    jti: v4(),
  },
  privateKey,
);

KJUR工具函数代码

const generateSignedRequestJWT = (sign, header, payload, privateKey) => {
  if (!header || !payload || !privateKey) {
    throw new Error(
      `utils.generateSignedRequestJWT - Missing required argument. Following arguments have been provided: header: ${!!header}, payload: ${!!payload}, privateKey: ${!!privateKey}`,
    );
  }
  try {
    // KJUR.jws.JWS.sign
    return sign(
      header.alg,
      JSON.stringify(header),
      JSON.stringify(payload),
      privateKey,
    );
  } catch (err) {
    console.error(
      'utils.generateSignedRequestJWT - Failed to generate signed request JWT',
      err,
      {
        header,
        payload,
        privateKeyDefined: !!privateKey,
      },
    );
    return null;
  }
};

排查方向

  1. 密钥格式差异
    检查Dart代码中传入的privateKey是否严格符合PKCS#8或PKCS#1格式,带正确的PEM头(-----BEGIN PRIVATE KEY-----)和尾,无多余换行/空格。KJUR可能自动兼容格式问题,但Dart库对格式要求更严格,可尝试转换密钥为PKCS#8格式后再传入。

  2. JSON序列化细节
    对比两个库生成的JWT头部、载荷的Base64Url解码结果:

    • 检查字段顺序、空格处理是否完全一致。部分严格的IDP会依赖JSON序列化的字段顺序(尽管JWT标准无此要求),可在Dart中手动指定序列化字段顺序,对齐KJUR的输出。
  3. 时间戳时区问题
    Dart的DateTime.now()默认是本地时间,转换的时间戳可能与KJUR用UTC时间生成的戳存在偏差。将Dart代码中的时间戳计算改为:

    final current_time = DateTime.now().toUtc();
    

    确保生成的exp、iat为UTC时间戳,避免IDP因时间偏差拒绝验证。

  4. PS512算法参数差异
    不同库对RSA-PSS(PS512)的盐长度默认值可能不同,KJUR默认盐长度与哈希长度一致(512位),可查看Dart库文档,手动指定盐长度为64字节(512位),对齐KJUR的算法参数。

  5. Base64Url编码细节
    检查两个库生成的Base64Url是否严格遵循RFC 4648标准:确认+替换为-、/替换为_,且移除了末尾的=填充,避免编码细节差异导致签名验证失败。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 09:42:07