Node.js中如何为AWS Cognito添加客户端密钥验证令牌及配置SDK?
AWS Cognito Node.js REST包装器:客户端密钥集成与令牌验证方案
好的,我来一步步帮你解决这个问题——我之前在搭建类似的Cognito认证服务时踩过不少坑,应该能帮到你。
一、如何将客户端密钥集成到SDK中?
首先要明确:当你的Cognito用户池客户端开启了客户端密钥(仅适用于服务器端/后台应用,SPA类应用不建议开启),Cognito要求你在发起认证请求时,必须传递一个SECRET_HASH参数,而不是直接把密钥“添加”到SDK实例里。这个哈希是用客户端密钥计算出来的,用于验证请求的合法性。
步骤1:计算SECRET_HASH
你可以用Node.js内置的crypto模块来生成这个哈希,公式是:SECRET_HASH = HMAC-SHA256(username + clientId, clientSecret) 转base64
代码示例:
const crypto = require('crypto'); function generateSecretHash(username, clientId, clientSecret) { return crypto .createHmac('sha256', clientSecret) .update(username + clientId) .digest('base64'); }
步骤2:发起认证请求时传递SECRET_HASH
假设你用的是AWS v3 SDK(@aws-sdk/client-cognito-identity-provider),在调用AdminInitiateAuth或InitiateAuth命令时,把生成的SECRET_HASH加入到AuthParameters中:
const { CognitoIdentityProviderClient, AdminInitiateAuthCommand } = require('@aws-sdk/client-cognito-identity-provider'); // 初始化Cognito客户端 const cognitoClient = new CognitoIdentityProviderClient({ region: process.env.COGNITO_REGION, }); // 处理/login请求的示例函数 async function loginUser(username, password) { const clientId = process.env.COGNITO_CLIENT_ID; const clientSecret = process.env.COGNITO_CLIENT_SECRET; const userPoolId = process.env.COGNITO_USER_POOL_ID; // 生成SECRET_HASH const secretHash = generateSecretHash(username, clientId, clientSecret); try { const command = new AdminInitiateAuthCommand({ UserPoolId: userPoolId, ClientId: clientId, AuthFlow: 'ADMIN_USER_PASSWORD_AUTH', // 根据你的认证流程调整 AuthParameters: { USERNAME: username, PASSWORD: password, SECRET_HASH: secretHash, // 关键:添加这个参数 }, }); const response = await cognitoClient.send(command); // 返回给客户端的令牌(IdToken、AccessToken等) return response.AuthenticationResult; } catch (error) { throw new Error(`认证失败:${error.message}`); } }
注意:如果你的用户池客户端没有开启客户端密钥,这一步可以跳过,不需要传递SECRET_HASH。
二、如何用客户端密钥验证令牌?
Cognito生成的令牌(尤其是ID Token)是JWT格式,验证时需要确认:
- 签名是否合法(用Cognito的公钥验证)
- 令牌是否过期
- 令牌的发行方(iss)、受众(aud)是否匹配你的用户池和客户端
这里推荐用jsonwebtoken结合jwks-rsa库来实现,它们可以自动从Cognito的JWKS端点获取公钥,不需要手动管理公钥。
步骤1:安装依赖
npm install jsonwebtoken jwks-rsa
步骤2:编写令牌验证函数
const jwt = require('jsonwebtoken'); const jwksClient = require('jwks-rsa'); // 配置JWKS客户端,指向你的Cognito用户池JWKS端点 const client = jwksClient({ jwksUri: `https://cognito-idp.${process.env.COGNITO_REGION}.amazonaws.com/${process.env.COGNITO_USER_POOL_ID}/.well-known/jwks.json`, }); // 获取公钥的辅助函数 function getKey(header, callback) { client.getSigningKey(header.kid, (err, key) => { if (err) return callback(err); const signingKey = key.getPublicKey(); callback(null, signingKey); }); } // 验证ID Token的函数 async function verifyIdToken(idToken) { return new Promise((resolve, reject) => { jwt.verify( idToken, getKey, { issuer: `https://cognito-idp.${process.env.COGNITO_REGION}.amazonaws.com/${process.env.COGNITO_USER_POOL_ID}`, audience: process.env.COGNITO_CLIENT_ID, // ID Token的aud是客户端ID algorithms: ['RS256'], // Cognito用RS256签名JWT ignoreExpiration: false, // 不忽略过期时间 }, (err, decoded) => { if (err) return reject(new Error(`令牌验证失败:${err.message}`)); resolve(decoded); // 返回解码后的Claims } ); }); } // 验证Access Token的函数(注意aud是用户池ID) async function verifyAccessToken(accessToken) { return new Promise((resolve, reject) => { jwt.verify( accessToken, getKey, { issuer: `https://cognito-idp.${process.env.COGNITO_REGION}.amazonaws.com/${process.env.COGNITO_USER_POOL_ID}`, audience: process.env.COGNITO_USER_POOL_ID, // Access Token的aud是用户池ID algorithms: ['RS256'], ignoreExpiration: false, }, (err, decoded) => { if (err) return reject(new Error(`令牌验证失败:${err.message}`)); resolve(decoded); } ); }); }
使用示例
在你的API中间件或者路由里,就可以调用这些函数验证客户端传递的令牌:
// 假设这是一个需要认证的API端点 app.get('/api/protected', async (req, res) => { try { const idToken = req.headers.authorization?.split(' ')[1]; // 从Authorization头获取Bearer令牌 if (!idToken) { return res.status(401).json({ message: '缺少令牌' }); } const decodedToken = await verifyIdToken(idToken); // 验证通过,可以继续处理请求,比如获取用户信息 res.json({ message: '访问成功', user: decodedToken }); } catch (error) { res.status(401).json({ message: error.message }); } });
最后几点提醒
- 密钥安全:客户端密钥绝对不能硬编码在代码里,一定要用环境变量(比如
dotenv)来存储,避免泄露。 - 令牌类型区分:ID Token用于验证用户身份,包含用户信息;Access Token用于访问Cognito资源或你的API,两者的
aud参数不同,验证时要注意区分。 - 错误处理:验证令牌时可能会遇到令牌过期、签名无效、Claims不匹配等错误,要在代码里妥善处理这些情况,返回合适的HTTP状态码。
内容的提问来源于stack exchange,提问作者David Alsh
相关产品推荐
相关产品推荐

