如何获取AWS Cognito用户池的JWK(JSON Web Key)?
解决AWS Cognito IdToken验证中JWK获取与匹配问题
核心问题分析
无法获取有效JWK的常见原因有两个:一是URI占位符未正确替换(比如<Region>和<userPoolId>没换成实际值);二是未根据IdToken的kid字段匹配对应JWK(JWKS返回的是密钥集合,需找到与当前token对应的密钥)。
完整实现步骤与代码
1. 正确构建JWKS URI
将<Region>替换为你的Cognito用户池所在区域(如us-west-2),<userPoolId>替换为用户池ID(格式类似us-west-2_abc123XYZ),最终URI示例:
https://cognito-idp.us-west-2.amazonaws.com/us-west-2_abc123XYZ/.well-known/jwks.json
2. 编写Express中间件
以下是完整的中间件代码,包含JWKS获取、缓存、kid匹配及IdToken验证逻辑:
const jwt = require('jsonwebtoken'); const jwtToPem = require('jwk-to-pem'); const axios = require('axios'); // 需先安装:npm install axios // 缓存JWKS,避免重复请求AWS接口 let cachedJwks = null; let cacheExpiry = 0; const cognitoAuthMiddleware = async (req, res, next) => { try { // 从Authorization头获取IdToken(推荐方式,格式为Bearer <IdToken>) const authHeader = req.headers.authorization; if (!authHeader || !authHeader.startsWith('Bearer ')) { return res.status(401).json({ message: '缺少有效IdToken' }); } const idToken = authHeader.split(' ')[1]; // 解析IdToken的header获取kid,无需验证签名 const decodedHeader = jwt.decode(idToken, { complete: true }).header; const kid = decodedHeader.kid; // 获取并缓存JWKS(缓存1小时) if (!cachedJwks || Date.now() > cacheExpiry) { const jwksUri = 'https://cognito-idp.<Region>.amazonaws.com/<userPoolId>/.well-known/jwks.json'; const response = await axios.get(jwksUri); cachedJwks = response.data.keys; cacheExpiry = Date.now() + 3600000; // 1小时后过期 } // 根据kid匹配对应的JWK const jwk = cachedJwks.find(key => key.kid === kid); if (!jwk) { return res.status(401).json({ message: '找不到匹配的JWK密钥' }); } // 转换JWK为PEM格式并验证token const pem = jwtToPem(jwk); const decodedToken = jwt.verify(idToken, pem, { algorithms: ['RS256'] }); // 将解码后的用户信息挂载到req对象,供后续路由使用 req.user = { email: decodedToken.email, userId: decodedToken.sub // 可按需添加其他字段 }; next(); } catch (err) { console.error('IdToken验证失败:', err.message); return res.status(401).json({ message: '身份验证失败' }); } }; module.exports = cognitoAuthMiddleware;
3. 使用中间件保护路由
在需要保护的路由中引入中间件:
const express = require('express'); const cognitoAuthMiddleware = require('./path/to/middleware'); const router = express.Router(); // 受保护路由,仅携带有效IdToken的请求可访问 router.get('/protected', cognitoAuthMiddleware, (req, res) => { res.json({ message: '访问受保护路由成功', userEmail: req.user.email }); }); module.exports = router;
关键注意事项
- IdToken传递方式:建议通过
Authorization请求头传递,比放在body更符合REST规范。 - 缓存策略:缓存JWKS可减少对AWS的请求次数,避免触发限流,同时需设置合理的过期时间。
- 错误处理:需捕获网络请求失败、kid不匹配、token过期/篡改等异常,返回对应的401或403状态码。
内容的提问来源于stack exchange,提问作者Shen Wony
相关产品推荐
相关产品推荐

