阅读主题
Android 可信授权交换
适用版本:v0.2.1。前置为已开通 Android App ID、仅保存在游戏服务器的应用密钥,以及可从设备访问的 HTTPS 游戏接口。客户端授权事务机制已有实现,首款外部游戏的实际闭环仍待接入验证。
完整链路
- 宿主调用
sdk.login(activity);原生登录验证后,SDK 校验 PKCE/state 并回调onAuthorization(Authorization authorization)。 - 宿主保留原始对象,将
authorization.toJson()发给自己的游戏服务端。 - 游戏服务端验证请求应用范围,携带
X-API-Key调用账号平台POST /api/server/auth/exchange;body 是{appId,code,codeVerifier}。 - 服务端使用可信兑换身份建立自己的游戏会话,并将 SDK 所需的
SessionResponse返回客户端。 - 客户端在仍属于原 SDK、原 Activity 时调用
completeGameLogin(authorization, responseJson)。
Authorization 的公开字段是 appId、code、codeVerifier;内部含登录 attempt/generation。构造函数不公开,不能自己重建对象或从 JSON 反序列化一个替代对象。平台密钥不能放进 APK、资源、远程公开配置或日志。
宿主 HTTPS 交换示例
将下面成员与方法放入实现 StarlightSdk.Listener 的 Activity。sdk 是 快速接入 中的成员字段;替换游戏服务器 URL。此示例使用 JDK 网络 API,无新增依赖;不是 SDK 自带网络方法。
java
private final java.util.concurrent.ExecutorService gameIo =
java.util.concurrent.Executors.newSingleThreadExecutor();
@Override public void onAuthorization(StarlightSdk.Authorization authorization) {
final StarlightSdk owner = sdk;
if (owner == null || isFinishing() || isDestroyed()) return;
gameIo.execute(() -> {
javax.net.ssl.HttpsURLConnection connection = null;
try {
connection = (javax.net.ssl.HttpsURLConnection) new java.net.URL(
"https://game.example.com/game-server/session").openConnection();
connection.setRequestMethod("POST");
connection.setConnectTimeout(15000);
connection.setReadTimeout(15000);
connection.setInstanceFollowRedirects(false);
connection.setDoOutput(true);
connection.setRequestProperty("Content-Type", "application/json");
byte[] payload = authorization.toJson().toString()
.getBytes(java.nio.charset.StandardCharsets.UTF_8);
connection.setFixedLengthStreamingMode(payload.length);
try (java.io.OutputStream out = connection.getOutputStream()) { out.write(payload); }
if (connection.getResponseCode() != 200)
throw new java.io.IOException("游戏服务器未完成授权交换");
java.io.ByteArrayOutputStream bytes = new java.io.ByteArrayOutputStream();
try (java.io.InputStream in = connection.getInputStream()) {
byte[] buffer = new byte[4096]; int count;
while ((count = in.read(buffer)) != -1) {
if (bytes.size() + count > 65536)
throw new java.io.IOException("授权响应过大");
bytes.write(buffer, 0, count);
}
}
final org.json.JSONObject result = new org.json.JSONObject(
new String(bytes.toByteArray(), java.nio.charset.StandardCharsets.UTF_8));
runOnUiThread(() -> {
if (sdk == owner && !isFinishing() && !isDestroyed())
owner.completeGameLogin(authorization, result);
});
} catch (Exception failure) {
runOnUiThread(() -> {
if (sdk == owner && !isFinishing() && !isDestroyed())
onError("GAME_EXCHANGE_FAILED", "登录交换失败,请重新登录");
});
} finally {
if (connection != null) connection.disconnect();
}
});
}这里 GAME_EXCHANGE_FAILED 是宿主自定义错误,不是 SDK 保证返回的错误码。onDestroy() 还需调用 gameIo.shutdownNow() 并释放 SDK;网络超时仍作为连接结束的边界,主线程回调中的实例/Activity 检查不可删除。不要忽略证书错误或启用不校验证书的 TrustManager。
返回数据与成功预期
返回给 completeGameLogin 的对象应是平台兑换得到的完整 SessionResponse:token、expiresAt、user、role。它不是自定义 {success:true} 包装,也不是只含游戏自身 token 的对象。Android 完成方法检查非空 token、user.id、role.id 及尚未到期的 expiresAt;检查只是输入防御,不能代替可信服务器兑换。
方法通过原始 Authorization 对象和内部 generation 拒绝过期/被取消事务;成功时清除待授权事务、保存内存快照并调用 onSessionChanged。宿主从可信 user.gameUserId 映射游戏玩家,勿凭客户端昵称或 role 文本创建特权会话。
错误、取消和重试
| 情况 | 预期 |
|---|---|
| 错误密钥/App ID/verifier、过期或已消费 code | 平台拒绝兑换;游戏后端返回失败;不调用成功完成 |
单参数 completeGameLogin(JSONObject) | AUTH_CONTEXT_REQUIRED,不会登录 |
| 返回空对象、缺 token/user.id/role.id 或 expiresAt 无效/过期 | INVALID_SESSION |
| 关闭登录、切号、detach/destroy 后旧成功返回 | 原事务失效;不能恢复旧身份 |
| 外部交换网络超时 | 显示失败/重新登录,不无限重复兑换同一个一次性 code |
平台已提供区服、可信角色绑定与持久化发货/回收协议,需游戏服务端主动接入并完成外部履约验收。当前没有向任意外部游戏推送封禁/退出的机制;平台身份互通不能保证存档互通。下一步按 生命周期 处理 Activity 与迟到响应,再完成 设备验收。