检测并防止网站上与账号相关的欺诈活动

本文档介绍了如何使用 reCAPTCHA 账号卫士 检测并防止网站上与账号相关的欺诈活动。

reCAPTCHA 可帮助您保护关键操作,例如登录和结账。不过,多种微妙形式的账号滥用可能会造成 通过观察特定用户在一整段时间内在网站上的行为进行检测 。reCAPTCHA 账号卫士可帮助识别这些类型 通过为网站创建专门针对网站的模型来检测 可疑行为趋势或活动变化。通过使用特定于网站的模型,reCAPTCHA 账号卫士可帮助您检测以下内容:

  • 可疑活动
  • 具有类似行为的账号
  • 来自已针对特定用户被标记为受信任设备的请求

根据 reCAPTCHA 账号卫士的分析和特定于网站的模型,您可以执行以下操作:

  • 限制或停用欺诈性账号。
  • 防止账号盗用尝试。
  • 减少成功的账号接管问题。
  • 仅向来自合法用户账号的请求授予访问权限。
  • 降低用户从其某部可信设备登录时遇到的阻碍。

准备工作

  1. 为 reCAPTCHA 准备好环境
  2. 创建基于得分的网站密钥

为您的网页配置 reCAPTCHA 账号保护程序

reCAPTCHA 账号卫士需要全面了解账号活动,才能有效检测。如需开始向 reCAPTCHA 账号卫士提交与账号相关的活动,以及创建和改进特定于您网站的模型,请执行以下操作:

  1. 启用横向遥测数据收集
  2. 报告关键用户操作
  3. 评估关键用户事件
  4. 为用户事件添加注释,以调整特定于您网站的模型

启用水平遥测数据收集

reCAPTCHA 账号卫士需要全盘查看用户信息 等操作,例如用户是否已登录或引导用户登录。启用 由 reCAPTCHA 被动收集横向遥测数据 账号卫士,请使用 在所有网页的后台中创建的基于得分的网站密钥 是用户工作流程的一部分

以下示例展示了如何在网页中加载 reCAPTCHA JavaScript 脚本。

    <head>
    <script src="https://www.google.com/recaptcha/enterprise.js?render=KEY_ID"></script>
    ....
    </head>

报告关键用户操作

为了检测可疑活动模式并更好地了解您网站上的典型活动模式,reCAPTCHA 账号防护工具需要关键用户操作的相关信息。 因此,报告 针对网页上的关键用户操作调用 grecaptcha.enterprise.execute()

我们建议报告所有关键用户操作,因为这有助于收集更多信号。对于您要举报的每项用户操作, 替换 grecaptcha.enterprise.execute()action 参数的值 以及一个描述用户操作的操作名称

下表列出了在报告关键用户操作时可以使用的操作名称。

操作名称 用户发起的事件或用户操作
LOGIN

登录该网站。

REGISTRATION 在网站上注册。
SECURITY_QUESTION_CHANGE 请求更改安全问题。
PASSWORD_RESET 请求重置密码。
PHONE_NUMBER_UPDATE 请求更新电话号码。
EMAIL_UPDATE 请求更新电子邮件地址。
ACCOUNT_UPDATE 请求更新账号相关信息,如详细联系信息。
TRIGGER_MFA 触发 MFA 质询的操作。
REDEEM_CODE 请求兑换代码。
LIST_PAYMENT_METHODS 提取付款方式列表。

以下示例展示了如何对电话号码更新调用 grecaptcha.enterprise.execute()

    <script>
    function onClick(e) {
      e.preventDefault();
      grecaptcha.enterprise.ready(async () => {
        const token = await grecaptcha.enterprise.execute('KEY_ID', {action: 'PHONE_NUMBER_UPDATE'});
      });
    }
    </script>
    

评估关键用户事件

当您针对用户操作调用 grecaptcha.enterprise.execute() 时,它会生成一个令牌。对于关键客户 用户事件,例如成功和失败的登录、注册以及对已登录用户的操作 用户,请创建评估来评估 grecaptcha.enterprise.execute() 调用的结果。通过 评估结果为您提供风险判定,供您决定如何处理 潜在的欺诈活动您可以采取的一些措施包括屏蔽可疑请求、质疑有风险的登录行为,以及调查感兴趣的账号。

reCAPTCHA 账号卫士要求您提供一个稳定的账号标识符,以便 将用户活动(如登录请求、登录请求和注册请求)归功于 特定账号。这有助于 reCAPTCHA 账号卫士了解用户活动模式,并为每个账号构建活动模型,以便更好地检测异常流量和滥用流量。

选择不经常被用户更改的稳定账号标识符 accountId 并在 projects.assessments.create 方法。这个稳定的账号标识符应该包含 与同一用户相关的所有事件都使用相同的值。您可以提供以下账号标识符:

用户标识符

是否每个账号都可以与固定的用户名、电子邮件地址或手机号码相关联 编号,您可以将其用作 accountId。当您提供此类跨网站标识符(可在多个网站中重复使用的标识符)时,reCAPTCHA 会使用这些信息,通过标记滥用账号标识符并利用与这些标识符相关的跨网站滥用行为模式知识,根据跨网站模型加强对用户账号的保护。

或者,如果您有一个与每个账号相关联的内部用户 ID,则可以 作为 accountId 提供。

经过哈希处理或加密

如果您没有每个账号唯一关联的内部用户 ID,则可以 任何稳定的标识符转换为不透明的网站专用账号标识符。reCAPTCHA 账号卫士仍需要此标识符来了解用户活动模式并检测异常行为,但不会与其他网站共享此标识符。

请选择任何稳定的账号标识符并将其设为不透明,然后再发送到 reCAPTCHA,具体方法是: 加密或哈希处理:

  • 加密(推荐):使用确定性对账号标识符进行加密 这种加密方法可生成稳定的密文。如需了解详细说明,请参阅确定性地加密数据。如果您选择对称加密而非哈希处理,则无需在用户标识符与相应的不透明用户标识符之间保留映射。 解密 reCAPTCHA 返回的不透明标识符,将其转换为用户标识符。

  • 哈希处理:我们建议使用 SHA256-HMAC 方法和您选择的自定义盐对账号标识符进行哈希处理。由于哈希值是单向的,因此您需要在生成的哈希值和用户标识符之间保持映射,以便将返回的经过哈希处理的账号标识符映射回原始账号。

除了为所有与账号相关的请求提供稳定的账号标识符之外,您还 可以针对某些特定请求提供额外的账号标识符,可能不稳定。 除 accountId 帮助之外,还提供特定于上下文的账号标识符 reCAPTCHA 账号卫士能更好地理解用户活动并检测账号 包版会试图确保您的用户账号安全无虞。当您提供其他标识符时,reCAPTCHA 会使用这些信息来标记滥用账号标识符,并利用与这些标识符相关的跨网站滥用行为模式知识,根据跨网站模型加强对用户账号的保护。例如,您可以提供以下信息:

  • 用作登录标识名的用户名、电子邮件地址或手机号码 请求

  • 用于多重身份验证请求的已验证电子邮件地址或电话号码

  • 用户在请求更新账号时提供的电子邮件地址或电话号码(主号码或辅助号码)

  • 用户在注册请求期间提供的电子邮件地址和电话号码

将所选的稳定账号标识符附加到 accountId 参数(位于 projects.assessments.create 方法。(可选) 使用 userIds 为相关请求提供额外的账号标识符 字段。

在使用任何请求数据之前,请先进行以下替换:

  • PROJECT_ID:您的 Google Cloud 项目 ID
  • TOKEN:从 grecaptcha.enterprise.execute() 调用返回的令牌
  • KEY_ID:与网站关联的 reCAPTCHA 密钥
  • ACCOUNT_ID:与您网站的用户账号唯一相关联的标识符
  • EMAIL_ADDRESS:可选。与此请求关联的电子邮件地址(如果有)
  • PHONE_NUMBER:可选。与此请求关联的手机号码(如果 任意
  • USERNAME:可选。与此请求关联的用户名(如果有)

HTTP 方法和网址:

POST https://recaptchaenterprise.googleapis.com/v1/projects/PROJECT_ID/assessments

请求 JSON 正文:

{
  "event": {
    "token": "TOKEN",
    "siteKey": "KEY_ID",
    "userInfo": {
      "accountId": "ACCOUNT_ID",
      "userIds": [
        {
          "email": "EMAIL_ADDRESS"
        },
        {
          "phoneNumber": "PHONE_NUMBER"
        },
        {
          "username": "USERNAME"
        }
      ]
    }
  }
}

如需发送请求,请选择以下方式之一:

curl

将请求正文保存在名为 request.json 的文件中,然后执行以下命令:

curl -X POST \
-H "Authorization: Bearer $(gcloud auth print-access-token)" \
-H "Content-Type: application/json; charset=utf-8" \
-d @request.json \
"https://recaptchaenterprise.googleapis.com/v1/projects/PROJECT_ID/assessments"

PowerShell

将请求正文保存在名为 request.json 的文件中,然后执行以下命令:

$cred = gcloud auth print-access-token
$headers = @{ "Authorization" = "Bearer $cred" }

Invoke-WebRequest `
-Method POST `
-Headers $headers `
-ContentType: "application/json; charset=utf-8" `
-InFile request.json `
-Uri "https://recaptchaenterprise.googleapis.com/v1/projects/PROJECT_ID/assessments" | Select-Object -Expand Content

您应该收到类似以下内容的 JSON 响应:

{
  "tokenProperties": {
    "valid": true,
    "hostname": "www.google.com",
    "action": "login",
    "createTime": "2019-03-28T12:24:17.894Z"
   },
  "riskAnalysis": {
    "score": 0.6,
  },
 "event": {
    "token": "TOKEN",
    "siteKey": "KEY",
    "userInfo": {
      "accountId": "ACCOUNT_ID"
    }
  },
  "name": "projects/PROJECT_NUMBER/assessments/b6ac310000000000",
  "accountDefenderAssessment": {
    "labels": ["SUSPICIOUS_LOGIN_ACTIVITY"]
  }
}

代码示例

Java

如需向 reCAPTCHA 进行身份验证,请设置应用默认凭据。 如需了解详情,请参阅为本地开发环境设置身份验证


import com.google.cloud.recaptchaenterprise.v1.RecaptchaEnterpriseServiceClient;
import com.google.protobuf.ByteString;
import com.google.recaptchaenterprise.v1.AccountDefenderAssessment.AccountDefenderLabel;
import com.google.recaptchaenterprise.v1.Assessment;
import com.google.recaptchaenterprise.v1.CreateAssessmentRequest;
import com.google.recaptchaenterprise.v1.Event;
import com.google.recaptchaenterprise.v1.ProjectName;
import com.google.recaptchaenterprise.v1.RiskAnalysis.ClassificationReason;
import com.google.recaptchaenterprise.v1.TokenProperties;
import com.google.recaptchaenterprise.v1.UserId;
import com.google.recaptchaenterprise.v1.UserInfo;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.UUID;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

public class AccountDefenderAssessment {

  public static void main(String[] args)
      throws IOException, NoSuchAlgorithmException, InvalidKeyException {
    // TODO(developer): Replace these variables before running the sample.
    // projectId: Google Cloud Project ID
    String projectId = "project-id";

    // recaptchaSiteKey: Site key obtained by registering a domain/app to use recaptcha
    // services.
    String recaptchaSiteKey = "recaptcha-site-key";

    // token: The token obtained from the client on passing the recaptchaSiteKey.
    // To get the token, integrate the recaptchaSiteKey with frontend. See,
    // https://cloud.google.com/recaptcha-enterprise/docs/instrument-web-pages#frontend_integration_score
    String token = "recaptcha-token";

    // recaptchaAction: The action name corresponding to the token.
    String recaptchaAction = "recaptcha-action";

    // Unique ID of the user, such as email, customer ID, etc.
    String accountId = "default" + UUID.randomUUID().toString().split("-")[0];

    // User phone number
    String phoneNumber = "555-987-XXXX";

    // User email address
    String emailAddress = "john.doe@example.com";

    accountDefenderAssessment(projectId, recaptchaSiteKey, token, recaptchaAction, accountId, phoneNumber, emailAddress);
  }

  /**
   * This assessment detects account takeovers. See,
   * https://cloud.google.com/recaptcha-enterprise/docs/account-takeovers The input is the hashed
   * account id. Result tells if the action represents an account takeover. You can optionally
   * trigger a Multi-Factor Authentication based on the result.
   */
  public static void accountDefenderAssessment(
      String projectId,
      String recaptchaSiteKey,
      String token,
      String recaptchaAction,
      String accountId,
      String phoneNumber,
      String emailAddress)
      throws IOException {
    try (RecaptchaEnterpriseServiceClient client = RecaptchaEnterpriseServiceClient.create()) {

      // Set the properties of the event to be tracked.
      Event.Builder eventBuilder =
          Event.newBuilder()
              .setSiteKey(recaptchaSiteKey)
              .setToken(token);

      // Set the account id, email address and phone number (of the user).
      eventBuilder.setUserInfo(
        UserInfo.newBuilder()
          .setAccountId(accountId)
          .addUserIds(UserId.newBuilder().setEmail(emailAddress))
          .addUserIds(UserId.newBuilder().setPhoneNumber(phoneNumber)));

      Event event = eventBuilder.build();

      // Build the assessment request.
      CreateAssessmentRequest createAssessmentRequest =
          CreateAssessmentRequest.newBuilder()
              .setParent(ProjectName.of(projectId).toString())
              .setAssessment(Assessment.newBuilder().setEvent(event).build())
              .build();

      Assessment response = client.createAssessment(createAssessmentRequest);

      // Check integrity of the response token.
      if (!checkTokenIntegrity(response.getTokenProperties(), recaptchaAction)) {
        return;
      }

      // Get the reason(s) and the reCAPTCHA risk score.
      // For more information on interpreting the assessment,
      // see: https://cloud.google.com/recaptcha-enterprise/docs/interpret-assessment
      for (ClassificationReason reason : response.getRiskAnalysis().getReasonsList()) {
        System.out.println(reason);
      }
      float recaptchaScore = response.getRiskAnalysis().getScore();
      System.out.println("The reCAPTCHA score is: " + recaptchaScore);
      String assessmentName = response.getName();
      System.out.println(
          "Assessment name: " + assessmentName.substring(assessmentName.lastIndexOf("/") + 1));

      // Get the Account Defender result.
      com.google.recaptchaenterprise.v1.AccountDefenderAssessment accountDefenderAssessment =
          response.getAccountDefenderAssessment();
      System.out.println(accountDefenderAssessment);

      // Get Account Defender label.
      List<AccountDefenderLabel> defenderResult =
          response.getAccountDefenderAssessment().getLabelsList();
      // Based on the result, can you choose next steps.
      // If the 'defenderResult' field is empty, it indicates that Account Defender did not have
      // anything to add to the score.
      // Few result labels: ACCOUNT_DEFENDER_LABEL_UNSPECIFIED, PROFILE_MATCH,
      // SUSPICIOUS_LOGIN_ACTIVITY, SUSPICIOUS_ACCOUNT_CREATION, RELATED_ACCOUNTS_NUMBER_HIGH.
      // For more information on interpreting the assessment, see:
      // https://cloud.google.com/recaptcha-enterprise/docs/account-defender#interpret-assessment-details
      System.out.println("Account Defender Assessment Result: " + defenderResult);
    }
  }

  private static boolean checkTokenIntegrity(
      TokenProperties tokenProperties, String recaptchaAction) {
    // Check if the token is valid.
    if (!tokenProperties.getValid()) {
      System.out.println(
          "The Account Defender Assessment call failed because the token was: "
              + tokenProperties.getInvalidReason().name());
      return false;
    }

    // Check if the expected action was executed.
    if (!tokenProperties.getAction().equals(recaptchaAction)) {
      System.out.printf(
          "The action attribute in the reCAPTCHA tag '%s' does not match "
              + "the action '%s' you are expecting to score",
          tokenProperties.getAction(), recaptchaAction);
      return false;
    }
    return true;
  }
}

解读关键用户事件的风险判定结果

如果您在启用账号保护程序的情况下创建评估,账号保护程序 返回 accountDefenderAssessment 作为评估响应的一部分。 accountDefenderAssessment 的值可帮助您评估用户活动是合法还是欺诈。它还会返回您在为用户事件添加注释时需要使用的评估 ID。

以下示例是 JSON 响应示例:

{
  "tokenProperties": {
    "valid": true,
    "hostname": "www.google.com",
    "action": "login",
    "createTime": "2019-03-28T12:24:17.894Z"
   },
  "riskAnalysis": {
    "score": 0.6,
  },
 "event": {
    "token": "TOKEN",
    "siteKey": "KEY_ID",
    "expectedAction": "USER_ACTION"
  },
  "name": "projects/PROJECT_ID/assessments/b6ac310000000000X",
  "accountDefenderAssessment": {
    labels: ["SUSPICIOUS_LOGIN_ACTIVITY"]
  }
}

accountDefenderAssessment 字段可以具有以下任意值:

说明
SUSPICIOUS_LOGIN_ACTIVITY 表示请求涉及高风险 包括撞库攻击或账号盗用
SUSPICIOUS_ACCOUNT_CREATION 表示请求代表 存在滥用账号创建的风险
PROFILE_MATCH

表示用户的属性与之前为此特定用户看到的属性匹配。此值表示此用户使用的是之前用于访问您网站的可信设备。

只有在以下情况下,系统才会返回 PROFILE_MATCH

  • 您可以使用多重身份验证 (MFA) 或双重身份验证 (2FA),reCAPTCHA 账号卫士会在用户通过 MFA 或 2FA 挑战后将用户个人资料标记为可信。
  • 您将评估注解为 LEGITIMATEPASSED_TWO_FACTOR,并且 reCAPTCHA 账号卫士会将相应的用户个人资料标记为可信。
RELATED_ACCOUNTS_NUMBER_HIGH 表示请求包含大量关联的账号。 这并不一定意味着该账号存在问题,但可能需要进一步调查。

为事件添加注释,以调整特定于您网站的模型

向 reCAPTCHA 账号卫士提供更多信息,并 改进网站专用的检测模型,则必须对 通过创建评估完成的评估

您可以通过向 projects.assessments.annotate 发送请求来为评估添加注释 方法。在该请求的正文中, 提供有关评估中描述事件的更多信息。

如需为评估添加注解,请执行以下操作:

  1. 确定要在请求 JSON 正文中添加的信息和标签 具体取决于您的应用场景

    下表列出了可用于添加注释的标签和值 事件:

    标签 说明 请求示例
    reasons 必需。用于支持您的评估的标签。

    在 事件发生后几秒或几分钟内显示的“reasons”标签 因为它们会影响实时检测。

    有关 可能的值, 请参阅原因值

    示例:要检测账号盗用,请在输入的 CORRECT_PASSWORD 的密码正确,或者 INCORRECT_PASSWORD 值。如果您部署了自己的多重身份验证 (MFA),则可以添加以下值:INITIATED_TWO_FACTORPASSED_TWO_FACTORFAILED_TWO_FACTOR

          {
          "reasons": ["INCORRECT_PASSWORD"]
          }
        
    annotation 可选。用于指示评估结果是否合法的标签。

    提供有关登录和注册事件的事实,以验证或更正 annotation 标签中的风险评估。

    可能的值:LEGITIMATEFRAUDULENT

    您可以随时发送此信息,也可以作为批量作业的一部分发送。 不过,我们建议您在几秒钟或几分钟内发送此信息 因为它们会影响实时检测。

          {
           "annotation": "LEGITIMATE"
          }
    
      
    accountId

    可选。用于将账号 ID 与事件相关联的标签。

    如果您创建的评估没有账号 ID,请使用此标签在有可用账号 ID 时提供事件的账号 ID。

      {
       "accountId": "ACCOUNT_ID"
      }
  2. 使用适当的标签创建注解请求。

    在使用任何请求数据之前,请先进行以下替换:

    • ASSESSMENT_ID:从 projects.assessments.create 调用返回的 name 字段的值。
    • ANNOTATION:可选。指示评估结果是合法还是欺诈的标签。
    • REASONS:可选。支持您的注解的原因。如需查看可能值的列表,请参阅原因值
    • ACCOUNT_ID:可选。与您网站上的用户账号相关联的专属标识符。

    如需了解详情,请参阅注解标签

    HTTP 方法和网址:

    POST https://recaptchaenterprise.googleapis.com/v1/ASSESSMENT_ID:annotate

    请求 JSON 正文:

    {
      "annotation": ANNOTATION,
      "reasons": REASONS,
      "accountId": ACCOUNT_ID
    }
    

    如需发送请求,请选择以下方式之一:

    curl

    将请求正文保存在名为 request.json 的文件中,然后执行以下命令:

    curl -X POST \
    -H "Authorization: Bearer $(gcloud auth print-access-token)" \
    -H "Content-Type: application/json; charset=utf-8" \
    -d @request.json \
    "https://recaptchaenterprise.googleapis.com/v1/ASSESSMENT_ID:annotate"

    PowerShell

    将请求正文保存在名为 request.json 的文件中,然后执行以下命令:

    $cred = gcloud auth print-access-token
    $headers = @{ "Authorization" = "Bearer $cred" }

    Invoke-WebRequest `
    -Method POST `
    -Headers $headers `
    -ContentType: "application/json; charset=utf-8" `
    -InFile request.json `
    -Uri "https://recaptchaenterprise.googleapis.com/v1/ASSESSMENT_ID:annotate" | Select-Object -Expand Content

    您应该会收到一个成功的状态代码 (2xx) 和一个空响应。

代码示例

Java

如需向 reCAPTCHA 进行身份验证,请设置应用默认凭据。 如需了解详情,请参阅为本地开发环境设置身份验证


import com.google.cloud.recaptchaenterprise.v1.RecaptchaEnterpriseServiceClient;
import com.google.protobuf.ByteString;
import com.google.recaptchaenterprise.v1.AnnotateAssessmentRequest;
import com.google.recaptchaenterprise.v1.AnnotateAssessmentRequest.Annotation;
import com.google.recaptchaenterprise.v1.AnnotateAssessmentRequest.Reason;
import com.google.recaptchaenterprise.v1.AnnotateAssessmentResponse;
import com.google.recaptchaenterprise.v1.AssessmentName;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.util.UUID;

public class AnnotateAccountDefenderAssessment {

  public static void main(String[] args) throws IOException, NoSuchAlgorithmException {
    // TODO(developer): Replace these variables before running the sample.
    // projectID: GCloud Project id.
    String projectID = "project-id";

    // assessmentId: Value of the 'name' field returned from the CreateAssessment call.
    String assessmentId = "account-defender-assessment-id";

    // accountId: Set the accountId corresponding to the assessment id.
    String accountId = "default" + UUID.randomUUID().toString().split("-")[0];

    annotateAssessment(projectID, assessmentId, accountId);
  }

  /**
   * Pre-requisite: Create an assessment before annotating. Annotate an assessment to provide
   * feedback on the correctness of recaptcha prediction.
   */
  public static void annotateAssessment(
      String projectID, String assessmentId, String accountId) throws IOException {

    try (RecaptchaEnterpriseServiceClient client = RecaptchaEnterpriseServiceClient.create()) {
      // Build the annotation request.
      // For more info on when/how to annotate, see:
      // https://cloud.google.com/recaptcha-enterprise/docs/annotate-assessment#when_to_annotate
      AnnotateAssessmentRequest annotateAssessmentRequest =
          AnnotateAssessmentRequest.newBuilder()
              .setName(AssessmentName.of(projectID, assessmentId).toString())
              .setAnnotation(Annotation.LEGITIMATE)
              .addReasons(Reason.PASSED_TWO_FACTOR)
              .setAccountId(accountId)
              .build();

      // Empty response is sent back.
      AnnotateAssessmentResponse response = client.annotateAssessment(annotateAssessmentRequest);
      System.out.println("Annotated response sent successfully ! " + response);
    }
  }
}

启用 reCAPTCHA 账号卫士

为网页配置 reCAPTCHA 账号卫士后,您就可以启用 reCAPTCHA 账号卫士了。

  1. 在 Google Cloud 控制台中,前往 reCAPTCHA 页面。

    前往 reCAPTCHA

  2. 验证项目名称是否显示在资源选择器中 。

    如果您没有看到项目名称,请点击资源选择器 然后选择您的项目。

  3. 点击 设置
  4. 账号卫士窗格中,点击配置

  5. Configure account Defender 对话框中,点击 启用,然后点击保存

启用 reCAPTCHA 账号卫士的操作可能需要几个小时才能传播到我们的系统。启用该功能的操作传播到我们的系统后,您将在评估过程中开始接收与账号卫士相关的响应。

后续步骤