Appearance
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_RESOLVED | SDK 返回 fake-IP,TCP/UDP 后续连接通过 data-server 隧道。 | 计入 Partner/Application/Installation 加速配额。 |
NOT_MANAGED | SDK 使用终端 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.mobile5. SDK 下载和完整性校验
Security HTTP 会提供版本化压缩包或制品库下载地址。解压后先核对校验和:
macOS:
bash
cd security-http-third-party-partner-<version>
shasum -a 256 -c SHA256SUMSLinux:
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.xcframeworkExamples/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 中:
- 为主 App 启用 App Groups。
- 创建 Network Extension target,Provider 类型为 Packet Tunnel。
- 为 Extension 启用 Network Extensions 和同一个 App Group。
- 将
SecHttp.xcframework同时链接到主 App target 和 Extension target,并设置为Do Not Embed;当前交付是静态 framework。 - Extension target 的
Other Linker Flags加入$(inherited) -all_load,避免SecHttp.PacketTunnelProvider和startTunnel等入口符号被裁剪。 - Extension 的
NSExtensionPrincipalClass配置为SecHttp.PacketTunnelProvider。 - 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_CREDENTIALS | Backend App Key/Secret 无效 | Backend 检查 Secret 和环境,不让用户反复重试 |
INVALID_BOOTSTRAP_TOKEN | Token 过期或已消费 | 重新向贵方 Backend 申请新 Token |
BOOTSTRAP_BINDING_MISMATCH | Token 与安装公钥不匹配 | 检查 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
- 检查 Control API DNS/TLS/防火墙。
- 检查 test/live App Key 和 Secret 是否配套。
- 检查 Idempotency-Key 是否唯一。
- 保存 Security HTTP 返回的 request ID 和错误码。
SDK 兑换失败
- 确认 Bootstrap Token 未被消费、未过期。
- 确认 Backend 使用当前设备刚生成的 thumbprint。
- Android 检查 package 和最终签名摘要。
- iOS 检查主 Bundle ID 和 Team ID。
- 检查设备时间是否准确。
VPN 无法连接
- 先看 SDK 错误码,再看 VPN status 的
last_error_code。 - 确认系统 VPN 权限、Android 通知/前台服务、iOS entitlement。
- 确认移动网络可解析和连接数据节点域名/端口。
- 检查证书 SAN 是否匹配节点 host。
- 提供时间、SDK 版本、平台版本、request ID、Installation ID 脱敏值和错误码给 Security HTTP;不要发送 Token 或 Secret。
14. Secret 轮换配合
Security HTTP 会创建第二个 active App Secret,通过安全渠道交付。贵方应:
- 将新 Secret 加入 Secret Manager。
- Backend 切换到新 Secret并观察 Bootstrap 成功率。
- 确认所有实例已切换后通知 Security HTTP 吊销旧 Secret。
- 切换失败时在旧 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 私钥、 完整终端用户身份信息。