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

如何使用@aws-sdk/client-cognito-identity-provider的InitiateAuth命令触发SRP认证后进入CUSTOM_CHALLENGE流程?

如何使用@aws-sdk/client-cognito-identity-provider的InitiateAuth命令触发SRP认证后进入CUSTOM_CHALLENGE流程?

我之前也碰到过类似的情况——一开始调用InitiateAuth直接跳自定义挑战,完全跳过了SRP的用户名密码验证。后来才搞明白问题出在两个地方:客户端调用的流程不对,还有用户池的触发器逻辑没配置到位。下面我一步步给你讲清楚怎么解决:

一、先确认用户池的基础配置

首先得确保你的Cognito用户池和应用客户端的认证流程是正确的:

  • 打开用户池的“应用集成”页面,找到你的应用客户端,在“认证流程”里勾选ALLOW_SRP_AUTH和ALLOW_CUSTOM_AUTH这两个选项,缺一不可。
  • 必须配置好三个Lambda触发器:Define Auth Challenge、Create Auth Challenge、Verify Auth Challenge Response,这三个是自定义挑战的核心,尤其是Define Auth Challenge的逻辑,要能正确判断什么时候进入自定义挑战。

二、客户端的正确调用流程(用@aws-sdk)

@aws-sdk本身没有封装SRP算法的实现,所以我们需要借助aws-cognito-identity-js来生成SRP所需的参数(自己写SRP算法太容易出错了)。下面是完整的调用步骤:

1. 生成SRP_A参数,发起SRP认证请求

首先用aws-cognito-identity-js的SRP类生成客户端公钥SRP_A,然后调用InitiateAuth,注意这里的AuthFlow必须是USER_SRP_AUTH,而不是CUSTOM_AUTH——这是很多人踩坑的点!

import { CognitoIdentityProviderClient, InitiateAuthCommand } from "@aws-sdk/client-cognito-identity-provider";
import { SRP } from "aws-cognito-identity-js";

const client = new CognitoIdentityProviderClient({ region: "你的区域" });
const userPoolId = "你的用户池ID";
const clientId = "你的应用客户端ID";
const username = "用户的用户名";
const password = "用户的密码";

// 生成SRP所需的参数
const srp = new SRP({
  username: username,
  password: password,
  poolName: userPoolId.split("_")[1], // 取用户池ID的后半部分作为poolName
  clientId: clientId
});
const srpA = srp.getSRPAModulus();

// 发起SRP认证请求
const initiateAuthCmd = new InitiateAuthCommand({
  AuthFlow: "USER_SRP_AUTH",
  ClientId: clientId,
  AuthParameters: {
    USERNAME: username,
    SRP_A: srpA,
    // 如果你的应用客户端设置了密钥,需要计算SECRET_HASH:HMAC-SHA256(用户名+客户端ID, 客户端密钥)
    // SECRET_HASH: "计算后的SecretHash"
  }
});

const initResponse = await client.send(initiateAuthCmd);

这一步如果用户名存在,Cognito会返回ChallengeName: "PASSWORD_VERIFIER",同时附带SRP验证所需的参数(比如USER_ID_FOR_SRP、SRP_B、SALT等)。

2. 处理PASSWORD_VERIFIER挑战,完成SRP验证

拿到上一步的响应后,我们需要计算SRP的验证签名,然后调用RespondToAuthChallenge来完成用户名密码的验证:

import { RespondToAuthChallengeCommand } from "@aws-sdk/client-cognito-identity-provider";

// 计算密码验证签名
const passwordVerifier = srp.getPasswordAuthenticationKey(
  initResponse.ChallengeParameters.USER_ID_FOR_SRP,
  initResponse.ChallengeParameters.SRP_B,
  initResponse.ChallengeParameters.SALT
);

// 发起挑战响应请求
const respondToChallengeCmd = new RespondToAuthChallengeCommand({
  ClientId: clientId,
  ChallengeName: "PASSWORD_VERIFIER",
  Session: initResponse.Session,
  ChallengeResponses: {
    USERNAME: username,
    PASSWORD_CLAIM_SECRET_BLOCK: initResponse.ChallengeParameters.SECRET_BLOCK,
    PASSWORD_CLAIM_SIGNATURE: passwordVerifier,
    TIMESTAMP: new Date().toISOString(),
    // 同样,有客户端密钥的话要带上SECRET_HASH
    // SECRET_HASH: "计算后的SecretHash"
  }
});

const challengeResponse = await client.send(respondToChallengeCmd);

这一步完成后,Cognito会验证用户名密码是否正确。如果验证通过,就会触发我们配置的Define Auth Challenge Lambda,这时候就该让它引导流程进入自定义挑战了。

三、关键:配置Define Auth Challenge触发器的逻辑

这个Lambda的作用是决定当前要发起什么挑战,以及是否发放令牌。我们需要让它判断:当用户完成了SRP的PASSWORD_VERIFIER挑战后,就发起CUSTOM_CHALLENGE;当自定义挑战验证通过后,就发放令牌。

下面是一个示例代码:

exports.handler = async (event) => {
  // 情况1:用户刚完成SRP的密码验证,还没进行自定义挑战
  if (event.request.session.length === 1 && event.request.session[0].challengeName === "PASSWORD_VERIFIER") {
    event.response.issueTokens = false;
    event.response.failAuthentication = false;
    event.response.challengeName = "CUSTOM_CHALLENGE";
  } 
  // 情况2:自定义挑战验证成功,发放令牌
  else if (event.request.session.length === 2 && 
           event.request.session[1].challengeName === "CUSTOM_CHALLENGE" && 
           event.request.session[1].challengeResult === true) {
    event.response.issueTokens = true;
    event.response.failAuthentication = false;
  } 
  // 情况3:自定义挑战失败,拒绝认证
  else if (event.request.session.length > 1 && 
           event.request.session[event.request.session.length - 1].challengeName === "CUSTOM_CHALLENGE" && 
           event.request.session[event.request.session.length - 1].challengeResult === false) {
    event.response.issueTokens = false;
    event.response.failAuthentication = true;
  } 
  // 默认情况:继续SRP流程(比如第一次发起请求时)
  else {
    event.response.issueTokens = false;
    event.response.failAuthentication = false;
    event.response.challengeName = "PASSWORD_VERIFIER";
  }
  return event;
};

至于Create Auth Challenge和Verify Auth Challenge Response的逻辑,就按照你自己的需求来写——比如在Create Auth Challenge里生成ABC-123456格式的OTP,然后发送给用户;在Verify Auth Challenge Response里验证用户输入的OTP是否正确。

四、最后处理自定义挑战

当RespondToAuthChallenge返回ChallengeName: "CUSTOM_CHALLENGE"时,就可以提示用户输入OTP,然后再次调用RespondToAuthChallenge:

const otp = "用户输入的ABC-123456格式的OTP";
const customChallengeCmd = new RespondToAuthChallengeCommand({
  ClientId: clientId,
  ChallengeName: "CUSTOM_CHALLENGE",
  Session: challengeResponse.Session,
  ChallengeResponses: {
    USERNAME: username,
    ANSWER: otp,
    // 有客户端密钥的话带上SECRET_HASH
    // SECRET_HASH: "计算后的SecretHash"
  }
});

const finalResponse = await client.send(customChallengeCmd);
// finalResponse里就包含了IdToken、AccessToken等令牌

这样整个流程就走通了:先通过SRP验证用户名密码,再进入自定义OTP挑战,最后发放令牌。

备注:内容来源于stack exchange,提问作者blimkt

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.22 14:24:32