Skip to content

Security HTTP 第三方移动 SDK 接入指南

适用:接入 Security HTTP Android AAR 或 iOS XCFramework 的第三方研发、测试和运维团队
接入模式:第三方 Backend 换票
安全红线:app_secret 只能存在于贵方 Backend,禁止进入 APK、AAB、IPA 或移动端配置

1. 接入后会发生什么

Security HTTP SDK 在贵方 App 内建立系统 VPN/Packet Tunnel,并使用短期 Token 连接 Security HTTP 数据节点。贵方终端用户不需要注册 Security HTTP 账号;用户身份由贵方 Backend 通过 partner_user_id 映射。

完整流程:

关键点:

  • app_key 是公开应用标识,可以配置在移动端。
  • app_secret 是长期秘密,只能保存在 Backend 的 Secret Manager/KMS。
  • bootstrap_token 有效期短、只能消费一次,并绑定某个安装实例公钥。
  • SDK Access Token 是短期 Token,由 SDK 自动续期,不向移动端发放长期 refresh token。
  • 安装实例私钥在 Android Keystore 或 iOS Secure Enclave/Keychain 生成,不导出。

2. 流量范围与域名级分流

2.1 当前对外语义

本版本第三方 SDK 的固定语义如下:

text
Android:ALL_APPS 捕获设备流量
iOS:Packet Tunnel 捕获系统交给 Extension 的流量

默认出口:DIRECT
仅“DNS 管理中已发布且可由 data-server 解析”的域名:TUNNEL

“捕获”不等于“全部经节点”。SDK 不会下载 DNS 快照,也不保存合作方域名白名单。每次 DNS 查询由 data-server 依据本机已应用的 DNS 发布快照返回结果:

data-server 结果后续行为可用性与计量
MANAGED_RESOLVEDSDK 返回 fake-IP,TCP/UDP 后续连接通过 data-server 隧道。计入 Partner/Application/Installation 加速配额。
NOT_MANAGEDSDK 使用终端 Wi-Fi/蜂窝网络 DNS 解析真实 IP,并通过 protected direct egress 连接。不进入 data-server,不计加速配额。
MANAGED_RESOLVE_FAILED,或 DNS 判定通道不可用显式失败,不能降级为直连。fail-closed,防止已登记业务错误绕过节点。

裸 IP、DoH/DoT、私有 DNS、代理或其他无法可靠关联域名的流量默认 DIRECT。SDK 自身的 Bootstrap/控制面、Token 续期及节点测速也使用底层网络,不属于业务加速流量。

2.2 域名如何生效

合作方需要加速的业务域名及其 DNS 记录,必须由合作方 Backend 或运营人员提交至 Security HTTP 既有 DNS 管理流程。记录通过校验和发布,并被节点 dnssync 原子应用后,才成为可加速域名。

  • 移动端不得从 Backend 接收域名列表、规则数或 DNS 快照。
  • 不支持合作方通过 SDK API 配置任意域名、CIDR、IP、协议或端口规则。
  • DNS 发布集合是全局共享集合,不按 Partner、Application、Installation 或来源 App 隔离。 因此合作方应只提交具有合法访问授权的业务域名;SDK 侧不能用应用包名限制其使用。
  • 规则格式、通配规则、审计、版本和回滚由 DNS 管理能力统一处理,合作方不应自行模拟白名单。

2.3 当前不提供的捕获模式

第三方 Android 高层 SecHttpClient 当前固定 ALL_APPS,不提供“仅宿主 App”或“指定 App” 公开配置;iOS 普通 Packet Tunnel 也不能识别流量来源 App。请不要围绕本版本设计应用选择 UI、 本地应用列表下发或“默认全隧道、白名单直连”逻辑。如确有 MDM Per-App VPN 或未来捕获范围需求, 请在接入前单独评估,不属于本版本标准交付范围。

3. 双方需要提供的内容

3.1 贵方需要提供给 Security HTTP

请填写交付包中的 ONBOARDING_INFORMATION_TEMPLATE.md

Android

  • test/live 应用的 Package Name。
  • 最终 App Signing Certificate SHA-256。
  • minSdk、targetSdk 和发布渠道。
  • 是否启用 Google Play App Signing。

Security HTTP 不需要、也不会索取 Android keystore、alias 密码或签名私钥。

iOS

  • 主 App Bundle ID。
  • Apple Team ID。
  • Packet Tunnel Extension Bundle ID。
  • App Group ID。
  • 最低 iOS 版本。

Security HTTP 不需要 Apple 账号、Distribution .p12 或签名私钥。

Backend 和容量

  • 贵方 Bootstrap Backend 的 HTTPS 域名。
  • 是否具备 Secret Manager/KMS。
  • 预计峰值在线会话、月流量、单会话带宽和主要区域。
  • test/live 技术联系人和事件响应联系人。

3.2 Security HTTP 会提供给贵方

  • Android AAR 和/或 iOS SDK 包。
  • 示例工程与完整接入验收壳。
  • test/live app_key
  • test/live app_secret,通过独立安全渠道仅显示/交付一次。
  • Control API Base URL,例如 https://api.example.com/api/v1
  • SDK 版本、兼容性说明和 SHA256SUMS
  • 测试节点、联调窗口和技术支持渠道。

不要把 App Secret 回复在普通邮件、群聊或工单正文中。收到后应立即存入 Secret Manager, 并删除本地明文副本。

4. 应用身份资料如何取得

4.1 Android 签名证书 SHA-256

从最终 APK 获取,推荐用于上线确认

bash
"$ANDROID_HOME/build-tools/35.0.0/apksigner" verify --print-certs app-release.apk

提供输出中的:

text
Signer #1 certificate SHA-256 digest

从 release keystore 获取

bash
keytool -list -v \
  -keystore /secure/path/release.jks \
  -alias release

只提供 SHA256 摘要,不提供 keystore。

若使用 Google Play App Signing,生产环境必须提供 Play Console App signing key certificate 的 SHA-256,不是 upload key certificate。debug、test、灰度和 live 若签名 不同,应提前说明并使用不同 Application 配置。

4.2 iOS Team 和标识

  • Team ID:Apple Developer Membership 页面中的 10 位 Team ID。
  • 主 Bundle ID:主 App target 的 PRODUCT_BUNDLE_IDENTIFIER
  • Extension Bundle ID:Packet Tunnel Extension target 的 Bundle ID。
  • App Group:主 App 和 Extension 共同启用的 App Group identifier。

通常建议:

text
Main Bundle ID:       com.partner.mobile
Extension Bundle ID:  com.partner.mobile.PacketTunnel
App Group ID:         group.com.partner.mobile

5. SDK 下载和完整性校验

Security HTTP 会提供版本化压缩包或制品库下载地址。解压后先核对校验和:

macOS:

bash
cd security-http-third-party-partner-<version>
shasum -a 256 -c SHA256SUMS

Linux:

bash
cd security-http-third-party-partner-<version>
sha256sum -c SHA256SUMS

若任何文件校验失败,不要继续集成,应重新下载并联系 Security HTTP。

交付包中的验收 APK 是使用验收包名和测试证书构建,只用于验证流程,不能直接作为贵方 生产 App,也不能代表贵方生产应用的签名身份。

6. 先实现合作方 Backend

移动端接入前,贵方必须先实现一个 Backend 接口,用于保存 App Secret 并向 Security HTTP 申请一次性 Bootstrap Token。

6.1 推荐的贵方 App 到 Backend 接口

路径可由贵方自行定义,例如:

http
POST /mobile/security-http/bootstrap
Authorization: Bearer <贵方用户登录凭证>
Content-Type: application/json

请求:

json
{
  "installation_public_key_thumbprint": "base64url-sha256"
}

Backend 应从已认证的用户会话生成 partner_user_id,不要相信移动端自行传入任意用户 ID。

响应给 App:

json
{
  "bootstrap_token": "bt_...",
  "expires_in": 300
}

6.2 Backend 调用 Security HTTP

http
POST /api/v1/sdk/partner/bootstrap-tokens
Authorization: Basic base64(app_key:app_secret)
Idempotency-Key: <每次业务请求唯一值>
Content-Type: application/json

请求:

json
{
  "partner_user_id": "贵方稳定且非个人信息的用户映射",
  "node_level_code": "vip",
  "installation_public_key_thumbprint": "base64url-sha256",
  "external_request_id": "贵方可追踪请求号",
  "limits_override": {
    "traffic_limit_bytes": 0
  }
}

limits_override 只能在合同上限内下调该安装实例额度。无单独限制时传 0node_level_code 用于控制该用户可见和可测速的节点范围:普通用户传 normal 或不传,VIP 传 vip,SVIP 传 svip。SDK 的 authenticate/listNodes/testAllNodes 都会返回该等级授权范围内的节点,移动端前端不需要自行过滤或传入节点列表。

响应:

json
{
  "bootstrap_token": "bt_...",
  "expires_at": "2026-06-11T12:05:00Z",
  "expires_in": 300
}

命令行示例:

bash
curl --fail-with-body \
  -u "$SECHTTP_APP_KEY:$SECHTTP_APP_SECRET" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H 'Content-Type: application/json' \
  -X POST "$SECHTTP_CONTROL_BASE_URL/sdk/partner/bootstrap-tokens" \
  -d "{
    \"partner_user_id\":\"$PARTNER_USER_ID\",
    \"node_level_code\":\"$NODE_LEVEL_CODE\",
    \"installation_public_key_thumbprint\":\"$THUMBPRINT\",
    \"external_request_id\":\"login-$(date +%s)\",
    \"limits_override\":{\"traffic_limit_bytes\":0}
  }"

6.3 Backend 安全要求

  • App Secret 仅从 Secret Manager/KMS 注入,不写入源码或 Docker image。
  • 只接受贵方已登录、已授权的 App 请求。
  • 对用户、IP 和设备执行限流、封禁和风控。
  • partner_user_id 使用内部稳定 ID 的 HMAC/不可逆映射,不直接发送手机号、邮箱等信息。
  • 日志只记录 request ID、HTTP 状态和稳定错误码,不记录 Secret、Bootstrap Token 或请求体。
  • 业务域名变更必须走 Security HTTP DNS 管理的提交、发布和回滚流程;不得把域名白名单随 Bootstrap、/sdk/config 或任意移动端接口下发。
  • Security HTTP 超时时,不要无限重试同一 Bootstrap Token;Bootstrap 创建请求可使用受控 重试和业务幂等策略。
  • 生产必须使用 HTTPS。不得通过移动端下发或回显 App Secret。

7. Android SDK 接入

7.1 环境要求

  • Android minSdk 23 或更高。
  • compileSdk 34 或更高。
  • 当前 native ABI 为 arm64-v8a
  • Java 8 字节码兼容。

7.2 添加 AAR

将:

text
Android/sechttp-android-<version>.aar

放入:

text
app/libs/

Gradle:

kotlin
dependencies {
    implementation(files("libs/sechttp-android-<version>.aar"))
}

宿主 App 建议配置:

xml
<application
    android:extractNativeLibs="false"
    ... />

AAR 已声明 VPN Service、Internet 和 foreground service 相关权限。Android 13+ 的通知权限 需要由贵方 Activity 按产品语境动态请求。

VPN 前台服务通知的标题默认使用贵方 App 的 android:label。如需自定义或本地化状态 文案,请在 app module 的 src/main/res/values/strings.xml 中覆盖 SDK 同名资源; 不需要修改或重新编译 AAR:

xml
<resources>
    <string name="sechttp_vpn_notification_title">贵方应用名称</string>
    <string name="sechttp_vpn_notification_connecting">正在建立安全连接</string>
    <string name="sechttp_vpn_notification_active">网络保护已开启</string>
    <string name="sechttp_vpn_notification_switching">正在切换节点</string>
    <string name="sechttp_vpn_notification_reconnecting">正在重新连接</string>
    <string name="sechttp_vpn_notification_error">安全连接暂不可用</string>
    <string name="sechttp_vpn_notification_disconnect">断开连接</string>
    <string name="sechttp_vpn_notification_channel_name">安全连接</string>
    <string name="sechttp_vpn_notification_channel_description">应用的加密网络连接</string>
</resources>

多语言文案使用 Android 标准的 values-zhvalues-en 等资源目录。修改覆盖文案后只需 重新构建贵方 APK/AAB。

7.3 初始化 SDK

java
SecHttpClient client = SecHttpClient.builder(applicationContext)
        .appKey("shpk_live_...")
        .apiBaseUrl("https://api.example.com/api/v1")
        .build();

正式 Partner SDK 强制使用 HTTPS 与数据节点证书校验,不提供关闭这两项校验的公开 API。 本地协议兼容性验证仅使用 Security HTTP 提供的独立 Android validation APK。

7.4 获取安装指纹并申请 Bootstrap Token

java
String thumbprint = client.getInstallationPublicKeyThumbprint();

将 thumbprint 发给贵方 Backend。Backend 返回 Bootstrap Token 后,先请求 Android VPN 权限:

java
Intent prepare = SecHttpVpnController.prepare(activity);
if (prepare != null) {
    vpnPermissionLauncher.launch(prepare);
} else {
    connectWithBootstrapToken();
}

如果不需要用户选择节点,用户授权后可继续使用兼容的一键连接:

java
client.connect(bootstrapToken, new SecHttpClient.ConnectCallback() {
    @Override
    public void onConnected(String installationId) {
        // SDK 已完成换票、策略/节点获取,并已请求启动 VPN Service。
        // 实际隧道是否 CONNECTED,以 SecHttpStatusListener 为准。
    }

    @Override
    public void onError(SecHttpException error) {
        showError(error.getCode(), error.getMessage());
    }
});

需要用户选择节点时,先换票获取节点列表。SDK 不会在这一步启动 VPN:

java
client.authenticate(bootstrapToken, new SecHttpClient.SessionCallback() {
    @Override
    public void onReady(String installationId, List<SecHttpClient.Node> nodes) {
        showNodes(nodes);
    }

    @Override
    public void onError(SecHttpException error) {
        showError(error.getCode(), error.getMessage());
    }
});

用户选择后只把 node ID 交回 SDK,不要自行使用或修改节点地址:

java
client.connectToNode(selectedNode.getId(), connectCallback);

运行中切换节点:

java
client.switchNode(selectedNode.getId(), new SecHttpSwitchCallback() {
    @Override
    public void onSuccess(SecHttpStatus status) {
        showConnected(status.getNodeName());
    }

    @Override
    public void onError(SecHttpException error) {
        showError(error.getCode(), error.getMessage());
    }
});

可调用 client.listNodes(callback) 刷新节点。SDK 会在连接和切换前重新向控制面解析 node ID,第三方 App 不得拼装 Access Token、Core JSON 或覆盖 node host/port。 状态同步可直接使用 client.getStatus()client.addStatusListener(...)client.removeStatusListener(...)

7.5 监听实际 VPN 状态

java
private final SecHttpStatusListener listener = status -> {
    switch (status.getState()) {
        case CONNECTED:
            showConnected(status.getNodeName(), status.getTxBytes(), status.getRxBytes());
            break;
        case RECONNECTING:
            showReconnecting();
            break;
        case ERROR:
            showError(status.getLastErrorCode(), status.getLastError());
            break;
        default:
            showState(status.getState().name());
    }
};

@Override
protected void onStart() {
    super.onStart();
    SecHttpVpnController.addStatusListener(listener);
}

@Override
protected void onStop() {
    SecHttpVpnController.removeStatusListener(listener);
    super.onStop();
}

7.6 额度、续期和断开

自动续期由 SDK 完成。验收或产品页面可查询额度:

java
client.queryQuota(new SecHttpClient.QuotaCallback() {
    @Override
    public void onSuccess(SecHttpClient.Quota quota) {
        // used / limit / periodEnd / exhausted
    }

    @Override
    public void onError(SecHttpException error) {
        showError(error.getCode(), error.getMessage());
    }
});

断开:

java
client.disconnect();

Activity/Service 生命周期结束且不再使用 SDK 时:

java
client.close();

不要在 close() 后复用该实例。

7.7 Android 节点测速、日志和 UDP

一键测速当前授权节点:

java
client.testAllNodes(new SecHttpClient.NodeTestCallback() {
    @Override
    public void onSuccess(List<SecHttpClient.NodeTestResult> results) {
        for (SecHttpClient.NodeTestResult result : results) {
            // latencyMs == -1 表示该节点全部探测失败。
            renderProbe(
                    result.getNode().getId(),
                    result.getLatencyMs(),
                    result.getPacketLossPercent());
        }
    }

    @Override
    public void onError(SecHttpException error) {
        showError(error.getCode(), error.getMessage());
    }
});

日志查询和监听:

java
List<SecHttpLogEntry> recent = client.getRecentLogs(200);
SecHttpLogListener listener = entry -> appendLog(entry.getLevel(), entry.getMessage());
client.addLogListener(listener);
client.removeLogListener(listener);
client.clearLogs();

日志位于当前进程内存环形缓冲区,最多保留 500 条,不写文件、不自动上传。上传诊断包前 仍必须由贵方做脱敏检查。

UDP/QUIC 不需要额外 API。AAR 内置 Go Core 会在 VPN TUN 内处理 TCP、DNS 和 UDP:已发布 域名的 UDP/QUIC 通过 data-server UDP tunnel stream 转发;未登记域名或裸 IP 使用 protected direct UDP socket。合作方不得把“UDP 可用”理解为所有 UDP 默认经节点。升级 UDP/QUIC 行为时 必须替换新版 AAR。

8. iOS SDK 接入

8.1 交付内容

iOS 包包含:

  • SecHttp.xcframework
  • Examples/SecHttpDemoApp/
  • 文档和校验和

当前交付以 SecHttp.xcframework 为准。示例工程用于参考主 App、Packet Tunnel Extension、App Group、entitlements 和 linker flags 配置;不要把 SDK 内部 Swift 源码 复制到业务工程后手工合并。完整工程配置见 iOS SDK 包内 Docs/THIRD_PARTY_IOS_XCFRAMEWORK_INTEGRATION_GUIDE.md

8.2 创建 Packet Tunnel Extension

在 Xcode 中:

  1. 为主 App 启用 App Groups。
  2. 创建 Network Extension target,Provider 类型为 Packet Tunnel。
  3. 为 Extension 启用 Network Extensions 和同一个 App Group。
  4. SecHttp.xcframework 同时链接到主 App target 和 Extension target,并设置为 Do Not Embed;当前交付是静态 framework。
  5. Extension target 的 Other Linker Flags 加入 $(inherited) -all_load,避免 SecHttp.PacketTunnelProviderstartTunnel 等入口符号被裁剪。
  6. Extension 的 NSExtensionPrincipalClass 配置为 SecHttp.PacketTunnelProvider
  7. Extension 增加最小 Swift 文件,执行 import SecHttp 并引用 PacketTunnelProvider.self;可直接参考示例工程的 ExtensionBootstrap.swift

将示例中的 Bundle ID、App Group 和 Team 替换为贵方真实值。

8.3 初始化

swift
let client = try SecHttpSDKClient(
    appKey: "shpk_live_...",
    apiBaseURL: URL(string: "https://api.example.com/api/v1")!,
    teamID: "ABCDE12345",
    providerBundleIdentifier: "com.partner.mobile.PacketTunnel",
    appGroupIdentifier: "group.com.partner.mobile",
    localizedDescription: "合作方应用"
)

正式 Partner SDK 强制使用 HTTPS 与证书校验,不提供不安全控制面或隧道开关。需要联调时, 请使用 Security HTTP 提供的独立 Android validation artifact;不得将测试开关带入生产 App。

8.4 获取指纹、选择节点并连接

swift
let thumbprint = try client.installationPublicKeyThumbprint()
let bootstrapToken = try await partnerBackend.bootstrap(thumbprint: thumbprint)
let session = try await client.authenticate(bootstrapToken: bootstrapToken)
showNodes(session.nodes)

let installationID = try await client.connectToNode(selectedNodeID)

前端只保存和传回 node.id,不要自行拼装节点地址或 Core 配置。刷新节点及运行中切换:

swift
let nodes = try await client.listNodes()
let status = try await client.switchNode(selectedNodeID)

switchNode 内部完成停止旧 Tunnel、等待系统释放、启动新节点以及目标节点状态确认。 成功返回时 status.state == .connectedstatus.nodeID == selectedNodeID;失败或 45 秒超时会完整停止 Tunnel,不自动回滚旧节点。

状态监听和错误处理与 Android 语义一致:

swift
let listenerToken = client.addStatusListener { status in
    showStatus(status)
}
client.removeStatusListener(listenerToken)

let errorToken = client.addErrorListener { error in
    showError(error.code, error.message)
}
client.removeErrorListener(errorToken)

do {
    try await client.switchNode(selectedNodeID)
} catch let error as SecHttpException {
    showError(error.code, error.message)
}

请始终使用 SecHttpException.code 分支处理,不要解析错误描述文本。 addErrorListener 对齐 Android onError,会稳定上报配额耗尽、认证失败、control API 非 2xx、网络超时和隧道启动失败。错误结构包含 codemessagephasenodeIDrequestIDhttpStatusretryableoccurredAt。运行时错误触发后, status.lastErrorCode/status.lastError 会保持与最近一次错误一致,直到下一次连接或清理。

首次启动会触发系统 VPN 配置授权。connectToNode 完成表示 SDK 已请求启动 Tunnel; 可通过 controller 查询实际状态:

swift
controller.status { status in
    // status.state / nodeName / txBytes / rxBytes / lastErrorCode
}

断开:

swift
client.disconnect()

8.5 iOS 节点测速、日志和 UDP

一键测速当前授权节点:

swift
let results = try await client.testAllNodes()
for result in results {
    // latencyMilliseconds == -1 表示该节点全部探测失败。
    renderProbe(
        nodeID: result.node.id,
        latency: result.latencyMilliseconds,
        packetLoss: result.packetLossPercent
    )
}

日志查询和监听:

swift
let recent = try await client.recentLogs(limit: 200)
let logToken = client.addLogListener { entry in
    appendLog(entry.level, entry.message)
}
client.removeLogListener(logToken)
try await client.clearLogs()

iOS 日志位于 Packet Tunnel Extension 进程的内存环形缓冲区,主 App 通过 NETunnelProviderSession.sendProviderMessage 查询。Extension 停止后内存日志会丢失。 第三方 SDK 的 control API 失败也会写入 recentLogs,字段包含脱敏后的 pathhttp_statusrequest_idsdk_error_code;日志不会包含 access_token

UDP/QUIC 不需要额外 API。XCFramework 内置的 packet-flow bridge 会把系统 NEPacketTunnelFlow 中的 UDP 包交给 Go Core:已发布域名通过数据节点 UDP tunnel stream 转发;未登记域名或裸 IP 使用受保护直连。iOS 的判断基于目标域名,不能据此推断来源 App。 升级 UDP/QUIC 行为时必须替换新版 SecHttp.xcframework

8.6 iOS 真机要求

模拟器构建只能验证编译,不能替代 Packet Tunnel 真机验收。必须准备:

  • 已加入贵方 Apple Team 的真实设备。
  • 主 App 和 Extension provisioning profiles。
  • App Groups capability。
  • Network Extensions entitlement:packet-tunnel-provider
  • 与 Security HTTP 后台登记一致的主 Bundle ID 和 Team ID。

9. 直接使用验证壳

建议在改造贵方正式 App 前,先用交付包中的验证壳确认网络、凭据、节点和票据链。

9.1 Android 验收 APK

bash
adb install -r Android/security-http-android-validation-<version>.apk

页面填写 Security HTTP 提供的验收 Partner Backend URL、可选 Demo Client Token 和测试 用户 ID,然后点击“完整换票并连接 VPN”。

可直接验证:

  • Backend 配置发现。
  • Bootstrap Token 创建与 SDK 兑换。
  • 节点和策略获取。
  • VPN 连接与流量状态。
  • 已发布域名经隧道、未登记域名和裸 IP 直连的路径验证。
  • 手动续期和额度查询。
  • 重复 Bootstrap Token 被拒绝。
  • Installation 吊销。
  • 小额度耗尽。

9.2 iOS 验收工程

打开:

bash
open Examples/iOSValidationShell/SecHttpDemoApp.xcodeproj

替换 Team、Bundle ID、Extension Bundle ID 和 App Group 后运行真机。验证壳源码可以直接 参考以下实现:

  • ContentView.swift:完整 UI 编排和异常按钮。
  • PartnerDemoAPI.swift:App 调用合作方 Backend 的示例。
  • SecHttpSDKClient.swift:换票、节点、VPN 和续期。

验收 Backend 和 Demo Client Token 只用于联调,不应复制为生产认证方案。

10. 错误处理

SDK/API 错误至少展示稳定 code,内部日志附带 request ID,但不要记录 Token。

错误码含义建议处理
INVALID_APPLICATION_CREDENTIALSBackend App Key/Secret 无效Backend 检查 Secret 和环境,不让用户反复重试
INVALID_BOOTSTRAP_TOKENToken 过期或已消费重新向贵方 Backend 申请新 Token
BOOTSTRAP_BINDING_MISMATCHToken 与安装公钥不匹配检查 thumbprint 是否串设备/缓存错误
INVALID_INSTALLATION_PROOF安装签名或续期证明无效检查设备时间、密钥是否重建;必要时重新授权
SDK_APPLICATION_OR_INSTALLATION_INACTIVE应用或安装实例停用联系双方运维,禁止无限重试
SDK_NODE_UNAVAILABLE无可用节点稍后重试并联系 Security HTTP
SDK_QUOTA_EXHAUSTED有效额度耗尽停止 VPN 和自动重连,展示额度提示
SDK_RATE_LIMITED请求过快按响应策略退避,不并发申请大量 Token
BOOTSTRAP_STORE_UNAVAILABLE授权缓存暂不可用Backend 短暂退避,保留 request ID 报障

永久的 401/吊销/额度错误不应无限自动重试。网络超时和 5xx 可使用有上限的指数退避。

11. 必测验收清单

正常流程

  • [ ] App 登录贵方账号后才能申请 Bootstrap Token。
  • [ ] Android/iOS 生成稳定安装指纹。
  • [ ] SDK 成功获得 Installation ID。
  • [ ] 节点获取成功,实际 VPN 状态进入 CONNECTED
  • [ ] 已发布域名的真实业务请求通过隧道,tx/rx 计数变化。
  • [ ] 已发布域名的 TCP、UDP/QUIC 产生 data-server 流量与配额;未登记域名与裸 IP 不产生 data-server DATA/UDP 流。
  • [ ] 如业务使用 HTTP/3,使用真实 HTTP/3 服务完成握手、请求和回包验证;不要只以普通 UDP 回显替代 QUIC 兼容性验收。
  • [ ] 已登记域名解析失败时业务明确失败,不能悄然改走本地网络。
  • [ ] Access Token 续期成功,连接可继续或正常重连。
  • [ ] 用户主动断开后系统 VPN 状态清理。

安全和异常

  • [ ] APK/IPA、源码和配置中搜索不到 App Secret。
  • [ ] 同一 Bootstrap Token 第二次兑换失败。
  • [ ] 吊销 Installation 后续期和新连接失败。
  • [ ] 禁用 Application 后新授权失败。
  • [ ] 配额耗尽后 VPN 停止且不无限重连。
  • [ ] 断网、切 Wi-Fi/蜂窝网络后状态正确;受管域名不得错误直连,未登记目标仍可按本地网络恢复。
  • [ ] Android force-stop/iOS Extension 重启后的状态符合产品设计。
  • [ ] 日志、埋点、崩溃报告不包含 Token、proof 或完整 partner_user_id。

12. 上线前检查

  • [ ] 使用 live App Key/Secret,不混用 test 环境。
  • [ ] Android 后台登记的是最终 App Signing SHA-256。
  • [ ] iOS 后台登记的是最终主 Bundle ID 和 Team ID。
  • [ ] App Secret 已存入生产 Secret Manager,并有轮换负责人。
  • [ ] Backend 使用 HTTPS,证书链和域名有效。
  • [ ] 移动端未启用任何 insecure testing 开关。
  • [ ] SDK 文件 SHA-256 与交付记录一致。
  • [ ] 已完成真机、真实移动网络和异常场景测试。
  • [ ] 双方值班、升级、吊销和故障联系渠道明确。

13. 常见排查顺序

Backend 能否创建 Bootstrap Token

  1. 检查 Control API DNS/TLS/防火墙。
  2. 检查 test/live App Key 和 Secret 是否配套。
  3. 检查 Idempotency-Key 是否唯一。
  4. 保存 Security HTTP 返回的 request ID 和错误码。

SDK 兑换失败

  1. 确认 Bootstrap Token 未被消费、未过期。
  2. 确认 Backend 使用当前设备刚生成的 thumbprint。
  3. Android 检查 package 和最终签名摘要。
  4. iOS 检查主 Bundle ID 和 Team ID。
  5. 检查设备时间是否准确。

VPN 无法连接

  1. 先看 SDK 错误码,再看 VPN status 的 last_error_code
  2. 确认系统 VPN 权限、Android 通知/前台服务、iOS entitlement。
  3. 确认移动网络可解析和连接数据节点域名/端口。
  4. 检查证书 SAN 是否匹配节点 host。
  5. 提供时间、SDK 版本、平台版本、request ID、Installation ID 脱敏值和错误码给 Security HTTP;不要发送 Token 或 Secret。

14. Secret 轮换配合

Security HTTP 会创建第二个 active App Secret,通过安全渠道交付。贵方应:

  1. 将新 Secret 加入 Secret Manager。
  2. Backend 切换到新 Secret并观察 Bootstrap 成功率。
  3. 确认所有实例已切换后通知 Security HTTP 吊销旧 Secret。
  4. 切换失败时在旧 Secret 尚未吊销前回滚。

Secret 轮换不需要发布新 App,因为 App Secret 从未进入移动端。

15. 获取支持时提供什么

请提供:

  • test/live 环境。
  • SDK 版本和平台/系统版本。
  • 大致发生时间和时区。
  • 稳定错误码、HTTP 状态、request ID。
  • Application ID 或 App Key 的前后少量字符。
  • Installation ID 的脱敏值。
  • VPN state、节点 ID、是否 Wi-Fi/蜂窝网络。
  • 可重复步骤。

请勿提供:App Secret、Access Token、Bootstrap Token、proof signature、Android/iOS 私钥、 完整终端用户身份信息。

Security HTTP SDK 官方对接技术文档