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

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格式,验证时需要确认:

  1. 签名是否合法(用Cognito的公钥验证)
  2. 令牌是否过期
  3. 令牌的发行方(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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.19 08:19:26