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

使用RS256算法时JWT签名无效问题排查(Azure AD场景)

Azure AD OAuth2.0 Access Token生成失败排查:Node.js JWT签名验证问题

在为Azure AD通过JWT令牌验证生成OAuth2.0 access token时遇到问题,已确认向Azure应用添加了正确证书,但始终报错。为排查根源,编写了本地Node.js程序测试JWT签名验证,结果失败证书的验证不通过;而有效证书在本地验证成功,且能从Azure AD生成access token。

重要提示:使用Java(bouncycastle和jose4j)时,两个证书都能正常工作,求排查TypeScript实现中遗漏的点。

测试代码片段

import crypto from 'crypto';
import fs from 'node:fs';
import jwt from "jsonwebtoken";

function verifySign() {

    let certificatePaths = {
      "validPublicCert" : '/Users/<my-path>/certs/certificate.pem',
      "validPrivateKey" : '/Users/<my-path>/certs/privateKey.pem',
      "failingPublicCert" : '/Users/<my-path>/certs/failing-public.pem',
      "failingPrivateKey" : '/Users/<my-path>/certs/failing-prv-key.key'
    };


    // read and create public certificate
    let publicCertificateBuffer = Buffer.from(fs.readFileSync(certificatePaths.failingPublicCert, 'utf8'));
    let x509publicCert = new crypto.X509Certificate(publicCertificateBuffer);

    // base64 encode sha1 hash of certificate
    let sha = crypto.createHash("SHA1");
    sha.update(x509publicCert.raw);
    let encodedCertificate = sha.digest('base64url');
    console.log("Encoded base64 public certificate is: " + encodedCertificate);

    // prepare jwt headers
    let jwtHeaders = {
        'x5t': encodedCertificate,
        'alg' : 'RS256',
        'typ' : 'JWT'
    };

    // prepare jwt payload
    const now = Math.floor(Date.now() / 1000);
    const expiresIn = now + 3 * 60; 
    let jwtPayload = {
        "iss": "d2057bd6-1560-4b16-9988-790e1f698d9b",
        "aud": "https://login.microsoftonline.com/<my-azure-ad-tenant-domain>/oauth2/v2.0/token",
        "sub": "d2057bd6-1560-4b16-9988-790e1f698d9b",
        "x5t": encodedCertificate,
        "typ": "JWT",
        "alg": "RS256",
        "exp" : expiresIn,
        "iat" : now,
        "nbf": now
      }

      // read encrypted private key and fetch/ build private key from it
      let encryptedPrivateKeyBuffer = fs.readFileSync(certificatePaths.failingPrivateKey, 'utf8');
      let encryptedKeyPassPhrase = '<my pass phrase>';
      let encryptedKey = crypto.createPrivateKey({
        key: encryptedPrivateKeyBuffer, 
        passphrase: encryptedKeyPassPhrase
      });
      let decryptedKey = encryptedKey.export({
        format: 'pem',
        type: 'pkcs1'
    });


    let assertion = jwt.sign(jwtPayload, decryptedKey, {
      algorithm: "RS256",
      header: jwtHeaders
  });

  console.log("JWT assertion created after signing: " + assertion);

  try {
    jwt.verify(assertion, publicCertificateBuffer);
  }catch(error: any) {
    console.log(error);
  }
  
}

verifySign();

证书与密钥对比

有效公钥

-----BEGIN CERTIFICATE-----
MIIEBzCCAu+gAwIBAgIUeg1UADx1kqu4xEPKNUjOpkAXihMwDQYJKoZIhvcNAQEL
BQAwgZIxCzAJBgNVBAYTAklOMQswCQYDVQQIDAJNSDENMAsGA1UEBwwEUHVuZTEf
MB0GA1UECgwWU2FpbHBvaW50IFRlY25ob2xvZ2llczETMBEGA1UECwwKSW5kaWEg
RW5nZzEQMA4GA1UEAwwHbkRMb2NhbDEfMB0GCSqGSIb3DQEJARYQbmRAc2FpbHBv
aW50LmNvbTAeFw0yMzA5MjkxNzUyMDBaFw0yNDA5MjgxNzUyMDBaMIGSMQswCQYD
VQQGEwJJTjELMAkGA1UECAwCTUgxDTALBgNVBAcMBFB1bmUxHzAdBgNVBAoMFlNh
aWxwb2ludCBUZWNuaG9sb2dpZXMxEzARBgNVBAsMCkluZGlhIEVuZ2cxEDAOBgNV
BAMMB25ETG9jYWwxHzAdBgkqhkiG9w0BCQEWEG5kQHNhaWxwb2ludC5jb20wggEi
MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCyYSil8dG2wB6zADSBiDfXjQGV
wBODyTb11qkIfI2/nwykkkpqIshnyEOLnaQz2Jrj3h6wzyFslyh+KXu8mxhKanER
+2BVfIMc0NQe5h4qgZoVC9nN2fAS/Pp5ya3lqAF8Bdz90KwR5RYKrNEAVDlno2kt
BxBP+/R08+6nhdZWiWj1dnjXQ+fbv468UaMb3I/oMaflj4LC+WBSI9N401v+SrHE
l1JUi9v4k0nME7NhbdDkFFJubsTdjXKNzCiWsc7BmXDi0HVLeFFPrsXvR2n6DzXu
a5Pmco3rZHqFm+vW+eYGt1yPzFxT/p5bnzBczHiPQiY4ZlwabFdIOxlvarE1AgMB
AAGjUzBRMB0GA1UdDgQWBBRohHr1iFHXGnYFd6vl7Zkh2+5JSjAfBgNVHSMEGDAW
gBRohHr1iFHXGnYFd6vl7Zkh2+5JSjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3
DQEBCwUAA4IBAQCei8uY2ipOk+3cZUCt1/WAbD0qqhMDv5XVHJmHu6zkj3z5K6Mg
o2YVM66IgtpcRzcEwcdkyLsQZg/T/3nVwo6okk/obYvbbByCLjq4yFNlHPdgZ6O6
ZDye89lBPhcWpaJio/hwwWmpFSUPUHuOeJrejSTU2MiiqlE6voRHGZ556WTsIHCQ
LqVUXf+Oy3cQ/3u96QCNgX2rFTpSdQi346p6OZ3z/j45OP8xTXa4BU4WQLiMunBm
/fWfvPax/C2ZkQh9vMvcbXgNQ5Y9y9lh5hnIhPhivXE/Rbunv2Sin7MQ+WG00Mhe
lf78iga1F+zYZ0cFAigoa0mSTvXVJNSOC8hq
-----END CERTIFICATE-----

有效私钥

-----BEGIN ENCRYPTED PRIVATE KEY-----
MII
<Remaining - Key>............
EUF7uoapGpNxXMFcqU18OQ==
-----END ENCRYPTED PRIVATE KEY-----

失败公钥

-----BEGIN CERTIFICATE-----
MIID8TCCAtmgAwIBAgIUY5l2EIzB1FnOeugBSWOf0t5kw2UwDQYJKoZIhvcNAQEL
BQAwgYcxCzAJBgNVBAYTAklOMQswCQYDVQQIDAJNSDENMAsGA1UEBwwEUHVuZTES
MBAGA1UECgwJU2FpbHBvaW50MQ0wCwYDVQQLDARFbmdnMQ4wDAYDVQQDDAVQb29q
YTEpMCcGCSqGSIb3DQEJARYacG9vamEuYmhhdHQxQHNhaWxwb2ludC5jb20wHhcN
MjEwMzEwMTA0NzM1WhcNMzEwMzA4MTA0NzM1WjCBhzELMAkGA1UEBhMCSU4xCzAJ
BgNVBAgMAk1IMQ0wCwYDVQQHDARQdW5lMRIwEAYDVQQKDAlTYWlscG9pbnQxDTAL
BgNVBAsMBEVuZ2cxDjAMBgNVBAMMBVBvb2phMSkwJwYJKoZIhvcNAQkBFhpwb29q
YS5iaGF0dDFAc2FpbHBvaW50LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
AQoCggEBAKNl8qxSUa9MvMx/a/KzXF8KyC16YCjbkTGSoGadik7t4ZSX3ZSjTysu
BHCWTwVll9OcQPiPmMv2sEWHWTzcg01CHeMUTgom4fotW1St67pgFWw7ZIo+6iVZ
leOJiM65Igm9Ge/f6zqEliFbMw76Gc5NFWsUxe+OmDQ41UGn8lwC1jT83mNwXj3F
yzy/ZnjHI+BPKmml0rskGkTyItMuKHkaf3iXVtvbWWCGNg3IXQgMLDTU7nzByHrw
gCI7hWnOnxExb87TzfTokkcqq5EGUHo74ycVv9Idv4PEz6GPoEceNb9XUR6pfe7C
AJDkdLo3Zv8helgPSLVdr3CHORgAOtsCAwEAAaNTMFEwHQYDVR0OBBYEFMj5V7ww
N3QMacvKfN8VPUO8guzCMB8GA1UdIwQYMBaAFMj5V7wwN3QMacvKfN8VPUO8guzC
MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAKDC9FAdmCwXzsrk
aDqXs6sprT9znFyxjv+tLfkU7d/OEKy3a21yzx3Hwt5NFYuBnjBZAwyqQBUuCCJU
lKc3+G7/+yUHKZ5hMvbc/cOWEuZrVBRUSiPh3IZCY9X969VdsM9ChrjCLnbapxdN
Ihhrx3b/WwBpX5r9cyUfOVyGaLIyRcXjtI6h5UqmjtQn4MZnKVBN99fxjLeoPT0i
XegN5oele2WuNpzG8i86K2YB6b+RtXpTrhOvGKcOv5S+3XeAQVfJ5hkOhJv4O12g
ZFAzlwM7lKmL0rQtZHx7cbTFLDfd0r6WsxRibiT1PTnRhY7qA9i2Oxdg+BJ6bFuY
OYDZ+zo=
-----END CERTIFICATE-----

失败私钥

-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-EDE3-CBC,86FAD4895E136D16

3sv
<Remaining Key>
FjBi0TncmddGjUI58I2TXxJZ/vOxQvbl1nQ+djFttLteIKN7R5YeiaUvNmYMXxB4
-----END RSA PRIVATE KEY-----

密钥格式差异

可以看到有效私钥采用的是PKCS#8加密格式(-----BEGIN ENCRYPTED PRIVATE KEY-----),而失败私钥是PKCS#1加密格式(-----BEGIN RSA PRIVATE KEY-----),由于对加密和JWT不熟悉,无法定位问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 19:33:09