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 只能在合同上限内下调该安装实例额度。无单独限制时传 0。 node_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-zh、values-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.PacketTunnelProvider 和 startTunnel 等入口符号被裁剪。
  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 == .connected 且 status.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、网络超时和隧道启动失败。错误结构包含 code、message、phase、 nodeID、requestID、httpStatus、retryable 和 occurredAt。运行时错误触发后, 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,字段包含脱敏后的 path、 http_status、request_id 和 sdk_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 官方对接技术文档