如何用Node.js的jsonwebtoken库验证Microsoft Entra访问令牌?
解决Microsoft Entra ID访问令牌签名验证失败(invalid signature)问题
以下是针对你遇到的签名验证错误的排查方向和修复方案:
1. 检查JWKS URI是否正确
如果你的应用是单租户,不能使用common端点,必须替换为你的租户ID或租户域名。common是多租户通用端点,若令牌由特定租户颁发,用它获取的密钥可能不匹配。
// 替换为你的租户ID const TENANT_ID = "your-tenant-id"; const client = jwksClient({ jwksUri: `https://login.microsoftonline.com/${TENANT_ID}/discovery/v2.0/keys`, });
2. 确认令牌类型是否正确
- 若你要验证用户身份,前端应传递ID令牌(id_token),而非访问令牌(access_token)。Entra ID的访问令牌默认是加密格式,无法用普通JWT库直接验证。
- 若必须验证访问令牌,需确保令牌的
aud声明是你的API客户端ID,且已在Entra ID中配置允许对该令牌进行签名验证。
3. 补充令牌核心声明验证
必须验证aud(受众)和iss(颁发者),这是令牌有效性的关键,否则即使签名通过,令牌也可能是非法的。修改jwt.verify的配置:
{ algorithms: options, allowInvalidAsymmetricKeyTypes: false, audience: "你的API客户端ID", // 替换为你的API客户端ID issuer: `https://login.microsoftonline.com/${TENANT_ID}/v2.0` }
4. 修复密钥获取的错误处理
原代码中getKey函数未传递异步错误,导致密钥获取失败时无法被捕获。修正后:
function getKey(header, callback) { client.getSigningKey(header.kid, function (err, key) { if (err) { return callback(err); // 传递错误给verify方法 } var signingKey = key.publicKey || key.rsaPublicKey; callback(null, signingKey); }); }
5. 验证令牌格式完整性
打印前端传递的令牌,确认没有截断、多余空格等格式问题。可先用jwt.decode(token, {complete: true})查看令牌头和负载,检查kid和alg是否与JWKS中的密钥匹配。
修改后的完整代码示例
const bodyParser = require("body-parser"); const express = require("express"); const cors = require("cors"); const jwt = require("jsonwebtoken"); const jwksClient = require("jwks-rsa"); const app = express(); app.use(bodyParser.json()); app.use(cors()); // 替换为你的租户ID和API客户端ID const TENANT_ID = "your-tenant-id"; const API_CLIENT_ID = "your-api-client-id"; app.post("/v1/teste", (req, res) => { const entraAccessToken = req.body.token; if (!entraAccessToken) { return res.status(400).send("Token missing"); } validaTokenJwks(entraAccessToken, (err, decoded) => { if (err) { console.log(`TOKEN VALIDATION FAILED: ${err}`); return res.status(401).send("Invalid token"); } console.log(`Token validated successfully:`, decoded); res.status(200).send("OK"); }); }); const options = ["RS256"]; const client = jwksClient({ jwksUri: `https://login.microsoftonline.com/${TENANT_ID}/discovery/v2.0/keys`, }); function getKey(header, callback) { client.getSigningKey(header.kid, function (err, key) { if (err) { return callback(err); } var signingKey = key.publicKey || key.rsaPublicKey; callback(null, signingKey); }); } function validaTokenJwks(token, callback) { jwt.verify( token, getKey, { algorithms: options, allowInvalidAsymmetricKeyTypes: false, audience: API_CLIENT_ID, issuer: `https://login.microsoftonline.com/${TENANT_ID}/v2.0` }, callback ); } app.listen(3000, () => { console.log("Server running on port 3000"); });
内容的提问来源于stack exchange,提问作者Pedro Ignacio
相关产品推荐
相关产品推荐

