> ## Documentation Index
> Fetch the complete documentation index at: https://documentation.onesignal.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 身份验证

> 通过要求服务器生成的 JWT 来验证外部用户 ID、电子邮件和短信订阅，防止用户冒充。

## 概述

OneSignal提供了一个名为身份验证的增强安全功能，以帮助防止用户冒充。此功能使用JSON Web Tokens（即[JWTs](https://jwt.io/introduction)），在您的服务器上安全生成。为了验证订阅信息，这些令牌被传递到您的应用和OneSignal的API。

启用身份验证以保护：

* 用户登录
* 添加电子邮件订阅
* 添加短信订阅
* 修改用户身份

<Warning>
  身份验证目前处于测试阶段。联系 `support@onesignal.com` 为您的帐户启用此功能。支持团队启用后，您可以在控制台中激活它（请参阅[第 5 步](#enable-token-identity-verification-in-the-dashboard)）。
</Warning>

## 先决条件

* 已配置推送平台的现有OneSignal应用。

* 与支持的SDK之一集成的移动应用：

  * [OneSignal Android SDK 5.2.0+](https://github.com/OneSignal/OneSignal-Android-SDK/releases)
  * [OneSignal iOS SDK 5.3.0+](https://github.com/OneSignal/OneSignal-iOS-SDK/releases)

<Note>
  包装 SDK 支持（Flutter、React Native、Unity 等）即将推出。
</Note>

## 设置

<Steps>
  <Step title="生成新密钥">
    登录您的OneSignal帐户并导航到**设置 > 密钥和ID > 身份验证**。

    <Frame caption="身份验证配置">
      <img src="https://mintcdn.com/onesignal/Z6xkXGfmy814If53/images/docs/d6f1009-keys_and_ids.png?fit=max&auto=format&n=Z6xkXGfmy814If53&q=85&s=ffc30e4a1f08dcbe4e6a2e4935a7f5de" alt="Settings page showing Keys and IDs with Identity Verification section" width="2420" height="1614" data-path="images/docs/d6f1009-keys_and_ids.png" />
    </Frame>

    点击**生成新密钥**以创建新的密钥对。

    <Frame caption="创建新密钥对">
      <img src="https://mintcdn.com/onesignal/56ctKxZSV4m5VEkn/images/docs/b24c1fd-Identity_Verification.png?fit=max&auto=format&n=56ctKxZSV4m5VEkn&q=85&s=73e56aa60fdef2dc8152bf929ebc1d52" alt="Generate New Keys button in the Identity Verification section" width="2350" height="686" data-path="images/docs/b24c1fd-Identity_Verification.png" />
    </Frame>

    下载PEM文件或复制私钥，确保安全存储私钥。

    <Frame caption="身份验证密钥对">
      <img src="https://mintcdn.com/onesignal/56ctKxZSV4m5VEkn/images/docs/bc217af-id_keys.png?fit=max&auto=format&n=56ctKxZSV4m5VEkn&q=85&s=b8e99ccdb2f4aafd453063540148ca2e" alt="Identity Verification key pair with private key and PEM download" width="1128" height="966" data-path="images/docs/bc217af-id_keys.png" />
    </Frame>

    <Warning>
      始终将私钥存储在安全环境中，例如密钥管理系统。永远不要在客户端代码、公共仓库或日志中暴露私钥。
    </Warning>
  </Step>

  <Step title="在后端生成验证JWT">
    身份验证需要在将终端用户登录到 OneSignal **之前**，先使用您的认证服务器对终端用户进行身份验证。当终端用户通过您的后端进行身份验证时，生成令牌并将其包含在对设备的认证响应中。如果您的应用没有运行后端服务器，请考虑设置一个轻量级服务器来验证用户并生成这些令牌。

    #### JWT 载荷

    JWT 可以具有以下属性：

    <ParamField path="iss" required="true">
      您的 OneSignal 应用 ID
    </ParamField>

    <ParamField path="exp" required="true">
      令牌过期日期。
    </ParamField>

    <ParamField path="identity" required="true">
      用户的别名。
    </ParamField>

    <ParamField path="subscriptions">
      仅在为用户添加电子邮件和短信订阅时才需要。
    </ParamField>

    #### JWT 签名

    使用 ES256 算法对 JWT 进行签名。确保您的后端配置为使用此签名方法，以避免将 JWT 发送到 OneSignal 时出现验证问题。我们建议使用 [JWT 库](https://jwt.io/libraries) 来完成此操作。

    使用 [jsonwebtoken](https://www.npmjs.com/package/jsonwebtoken) 的示例：

    <CodeGroup>
      ```typescript Node.js theme={null}
      import jwt from 'jsonwebtoken';

      const APP_ID = process.env['ONESIGNAL_APP_ID']
      const IDENTITY_VERIFICATION_SECRET = process.env['ONESIGNAL_IDENTITY_VERIFICATION_SECRET_KEY']

      // Generates JWT, potentially with subscription claims, for the user identified by the External ID
      function signOneSignalJWT(externalId, subscriptions) {
      return jwt.sign({
      iss: APP_ID,
      exp: Math.floor(Date.now() / 1000) + 3600, // 1-hour expiration
      identity: {
      'external_id': externalId,
      },
      subscriptions
      },
      IDENTITY_VERIFICATION_SECRET,
      { algorithm: 'ES256' });
      }

      // Pass this token to your mobile app to use with the `login` SDK method
      const onesignalJWT = signOneSignalJWT('EXTERNAL_ID');

      ```
    </CodeGroup>

    私钥位于我们从控制台下载的上一步文件中。

    #### 验证您的 JWT

    在与 SDK 集成之前，通过启动服务器并调用端点来验证 JWT 是否正确生成：

    <CodeGroup>
      ```typescript Node.js theme={null}
        node server.js
      ```
    </CodeGroup>

    <CodeGroup>
      ```bash cURL theme={null}
        curl http://localhost:3000/generate-jwt/your-external-id
      ```
    </CodeGroup>

    您应该收到如下响应：

    ```json theme={null}
    {
      "jwt": "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9..."
    }
    ```

    将 JWT 值粘贴到 [jwt.io](https://jwt.io) 并确认解码后的载荷包含以下参数：

    ```json theme={null}
    {
      "iss": "your-onesignal-app-id",
      "exp": 1234567890,
      "identity": {
        "external_id": "your-external-id"
      }
    }
    ```

    在进行下一步之前，确认以下内容：

    * `iss` 与您在 **设置 > 密钥和ID** 中的 OneSignal 应用 ID 匹配
    * `identity.external_id` 存在并与您将传递给 `OneSignal.login()` 的值匹配
    * `exp` 是未来的时间戳 — 过期的令牌将被 OneSignal 拒绝

    #### 包含订阅

    理想情况下，在用户登录时，订阅详细信息（如电子邮件或电话号码）应包含在 JWT 载荷中。如果这些详细信息不能预先获得，您的验证服务器必须提供端点以在订阅信息可用时动态生成令牌。

    示例：生成 JWT 以添加订阅

    <CodeGroup>
      ```typescript Node.js theme={null}
      const subscriptions = [
        {
            "type": "Email",
            "token": "[email protected]"
        },
        {
            "type": "SMS",
            "token": "+12345678"
        }
      ]
      const onesignalJWT = signOneSignalJWT('EXTERNAL_ID', subscriptions)
      ```
    </CodeGroup>
  </Step>

  <Step title="将 JWT 传递给 `login` 方法">
    一旦后端生成 JWT，就用它调用 `login` 方法。此令牌确保在进行任何更改（如添加电子邮件或短信订阅）之前验证用户身份。

    登录示例：

    <CodeGroup>
      ```java java theme={null}
      String externalId = "YOUR_EXTERNAL_ID";
      String onesignalJWT = "YOUR_JWT_TOKEN";

      OneSignal.login(externalId, onesignalJWT);
      ```

      ```kotlin kotlin theme={null}
      val externalId = "YOUR_EXTERNAL_ID"
      val onesignalJWT = "YOUR_JWT_TOKEN"

      OneSignal.login(externalId, onesignalJWT)
      ```

      ```swift Swift theme={null}
      let externalId = "YOUR_EXTERNAL_ID"
      let onesignalJWT = "YOUR_JWT_TOKEN"

      OneSignal.login(externalId: externalId, token: onesignalJWT)
      ```

      ```c objective-c theme={null}
      NSString* externalId = @"YOUR_EXTERNAL_ID";
      NSString* onesignalJWT = @"YOUR_JWT_TOKEN";

      [OneSignal login:externalId withToken:onesignalJWT];
      ```
    </CodeGroup>
  </Step>

  <Step title="处理 JWT 生命周期事件">
    您需要在后端实施专用端点来处理令牌失效等场景。当 OneSignal 请求更新时，此端点应提供刷新的 JWT。

    处理令牌失效和刷新 JWT 的示例：

    <CodeGroup>
      ```java java theme={null}
      OneSignal.addUserJwtInvalidatedListener(event -> {
        // Get the expired user's External ID
        String externalId = event.getExternalId();

        // Fetch a new JWT from your backend for the user
        String onesignalJWT = "yourUpdatedToken";

        // Provide the new JWT to the SDK
        OneSignal.updateUserJwt(externalId, onesignalJWT);
      });
      ```

      ```kotlin kotlin theme={null}
      OneSignal.addUserJwtInvalidatedListener(
         object : IUserJwtInvalidatedListener {
             override fun onUserJwtInvalidated(event: UserJwtInvalidatedEvent) {
               val externalId = event.externalId
               val onesignalJWT = ""
               updateUserJwt(externalId, onesignalJWT)
             }
         },
      )
      ```

      ```swift Swift theme={null}
      class AppDelegate: UIResponder, UIApplicationDelegate, OSUserJwtInvalidatedListener {
        func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
          // Set self to listen for JWT invalidated events
          OneSignal.addUserJwtInvalidatedListener(self)

          // Remove the JWT listener as needed by calling:
          // `OneSignal.removeUserJwtInvalidatedListener(self)`
        }

        // Required to conform to `OSUserJwtInvalidatedListener` protocol
        func onUserJwtInvalidated(event: OneSignalUser.OSUserJwtInvalidatedEvent) {
          // Get the expired user's External ID
          let externalId = event.externalId

          // Fetch a new JWT from your backend for the user
          let onesignalJWT = "yourUpdatedToken"

          // Provide the new JWT to the SDK
          OneSignal.updateUserJwt(externalId: externalId, token: onesignalJWT)
        }
      }
      ```

      ```c objective-c theme={null}
      @interface MyListener: NSObject<OSUserJwtInvalidatedListener>
      @end

      @implementation MyListener
      - (void)onUserJwtInvalidatedWithEvent:(OSUserJwtInvalidatedEvent * _Nonnull)event {
        // Get the expired user's External ID
        NSString *externalId = event.externalId;

        // Fetch a new JWT from your backend for the user
        NSString *onesignalJWT = @"yourUpdatedToken";

        // Provide the new JWT to the SDK
      	[OneSignal updateUserJwt:externalId withToken:onesignalJWT];
      }

      // Add or remove your User Jwt Invalidated Listener
      [OneSignal addUserJwtInvalidatedListener:myListener];
      [OneSignal removeUserJwtInvalidatedListener:myListener];
      ```
    </CodeGroup>

    这确保当用户的 JWT 失效时，可以从后端获取新的令牌并传递给 OneSignal。如果在身份验证期间创建的令牌不包含电子邮件和电话号码，您还可以使用此功能生成包含电子邮件和电话号码的令牌，以便管理电子邮件和短信订阅。
  </Step>

  <Step title="在控制台中启用令牌身份验证">
    从**设置 > 密钥和 ID**，切换**令牌身份验证**以启用。

    <Frame caption="启用令牌身份验证">
      <img src="https://mintcdn.com/onesignal/YOTSrtBSoqdrJ37A/images/docs/4887367-identity_verification_enabled.png?fit=max&auto=format&n=YOTSrtBSoqdrJ37A&q=85&s=26d06ca0a008e841d5206093bfbf2a8c" alt="Token Identity Verification toggle enabled in dashboard settings" width="2350" height="686" data-path="images/docs/4887367-identity_verification_enabled.png" />
    </Frame>

    启用后，您的应用必须发送 OneSignal JWT 来验证订阅的真实性。此外，您的应用需要使用由身份验证令牌服务器生成的 JWT 来调用 `login` 方法。
  </Step>
</Steps>

## 添加订阅

您无需额外步骤从移动应用添加订阅；调用登录方法会自动为您处理此操作。

<Tabs>
  <Tab title="添加电子邮件">
    <CodeGroup>
      ```java java theme={null}
      OneSignal.getUser().addEmail(emailAddress);
      ```

      ```kotlin kotlin theme={null}
      OneSignal.getUser().addEmail(emailAddress)
      ```

      ```swift Swift theme={null}
      // If you have not already included it in your JWT token, update the JWT with the email
      let onesignalJWT = "newTokenThatContainsTheEmailToAdd"
      OneSignal.updateUserJwt(externalId: externalId, token: onesignalJWT)

      // Add the email
      OneSignal.User.addEmail(emailAddress)
      ```

      ```c objective-c theme={null}
      // If you have not already included it in your JWT token, update the JWT with the email
      NSString *onesignalJWT = @"newTokenThatContainsTheEmailToAdd";
      [OneSignal updateUserJwt:externalId withToken:onesignalJWT];

      // Add the email
      [OneSignal.User addEmail:emailAddress];
      ```
    </CodeGroup>
  </Tab>

  <Tab title="添加电话号码">
    <CodeGroup>
      ```java java theme={null}
      OneSignal.getUser().addSms(smsNumber);
      ```

      ```kotlin kotlin theme={null}
      OneSignal.getUser().addSms(smsNumber)
      ```

      ```swift Swift theme={null}
      // If you have not already included it in your JWT token, update the JWT with the sms
      let onesignalJWT = "newTokenThatContainsTheSmsToAdd"
      OneSignal.updateUserJwt(externalId: externalId, token: onesignalJWT)

      // Add the sms number
      OneSignal.User.addSms(smsNumber)
      ```

      ```c objective-c theme={null}
      // If you have not already included it in your JWT token, update the JWT with the sms
      NSString *onesignalJWT = @"newTokenThatContainsTheSmsToAdd";
      [OneSignal updateUserJwt:externalId withToken:onesignalJWT];

      // Add the sms number
      [OneSignal.User addSms:smsNumber];
      ```
    </CodeGroup>
  </Tab>
</Tabs>

***

## REST API

当启用令牌身份验证时，对以下 API 的所有请求都必须在标头中包含服务器生成的 JWT 作为 bearer 令牌，例如：`Authorization: Bearer <JWT>`。

* [Create user](/reference/create-user)
* [View user](/reference/view-user)
* [Update user](/reference/update-user)
* [Delete user](/reference/delete-user)
* [View user identity](/reference/fetch-aliases)
* [Create alias](/reference/create-alias)
* [Delete alias](/reference/delete-alias)
* [Create subscription](/reference/create-subscription)
* [Update subscription](/reference/update-subscription)

***

## 常见问题

### 身份验证是必需的吗？

不，但强烈建议使用。没有它，任何知道用户外部 ID 的客户端都可以冒充该用户并修改其订阅或数据。

### 如果 JWT 在会话期间过期会怎样？

SDK 会触发 JWT 失效事件。实现 `addUserJwtInvalidatedListener`（请参阅[处理 JWT 生命周期事件](#handle-jwt-lifecycle-events)）以从后端获取刷新的令牌并将其传递给 `updateUserJwt`。

### 哪些 SDK 支持身份验证？

目前，原生 Android SDK（5.2.0+）和 iOS SDK（5.3.0+）。包装 SDK 支持（Flutter、React Native、Unity 等）即将推出。

### REST API 需要身份验证吗？

当令牌身份验证启用时，对[支持的 API](#rest-api) 的所有请求必须在 `Authorization` 标头中包含服务器生成的 JWT 作为 bearer 令牌。JWT 的生成方式与 SDK 使用相同。

### JWT 使用什么算法？

身份验证要求 JWT 使用 **ES256** 算法签名（使用 P-256 和 SHA-256 的 ECDSA）。其他算法将被拒绝。

***
