Cloud Save 客户端API接入指南
Cloud Save 客户端API接入指南
Cloud Save 客户端 API
Cloud Save 客户端 API 是面向不使用SDK的情况下集成 Cloud Save 的开发者提供的客户端集成的 API 列表。
API Endpoint: https://save.unity.cn
用户身份认证(JWT)
Cloud Save 客户端API 使用基于 JWT 的用户身份认证:
如何获取 JWT
未集成客户端 SDK 的开发者, 可使用 JWT API 来获取 JWT。
JWT API 基本信息
- 接口功能:
通过用户 UserID 获取登录后的 JWT - 请求方法:
POST - 请求地址:
https://p.unity.cn/v1/login/token - 请求体:
application/json
{
"userID": "<userId>", //需要获取 JWT 的玩家 id
"externalPersonaID": "<personaID>" //可选,非必填,需要获取 JWT 的角色 id
}- 响应数据
{
"accessToken": "<token>", //JWT(JSON Web Token), 可用于后续 API 的鉴权
"expiresAt": "<expiresAt>" //JWT 的过期时间
}- 是否需要认证:
需携带 Authorization
Nonce Authorization (推荐客户端程序使用)
使用 Nonce Authorization 来获取 JWT,步骤如下:
在 UOS 网站上获取当前需要使用的 UOS APP 的 AppId 和 AppSecret
注:此处 AppId 和 AppSecret,可在 UOS 网站上获取


在请求 JWT API 的 Request Header 中增加如下 Header:
- X-TIMESTAMP: 当前时间戳
- X-NONCE: 随机生成的UUID
- X-APPID: 当前需要使用的 UOS APP 的 AppId
- Authorization: nonce hexadecimal(sha256(appId + ":" + appSecret + ":" + X-TIMESTAMP + ":" + X-NONCE))
完整获取 JWT 参考示例如下:
C## C# 使用示例 - Nonce 授权方式 using System; using System.Collections.Generic; using System.Net.Http; using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; using Newtonsoft.Json; class Program { private const string LOGIN_TOKEN_URL = "https://p.unity.cn/v1/login/token"; public class TokenResponse { public string accessToken { get; set; } public object expiresAt { get; set; } } private static Dictionary<string, string> GetNonceAuthorization(string appId, string appSecret) { string nonce = Guid.NewGuid().ToString(); long timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); string signString = $"{appId}:{appSecret}:{timestamp}:{nonce}"; byte[] signBytes = Encoding.UTF8.GetBytes(signString); using (SHA256 sha256 = SHA256.Create()) { byte[] hashBytes = sha256.ComputeHash(signBytes); string token = BitConverter.ToString(hashBytes).Replace("-", "").ToLower(); return new Dictionary<string, string> { { "X-APPID", appId }, { "X-TIMESTAMP", timestamp.ToString() }, { "X-NONCE", nonce }, { "Authorization", $"nonce {token}" } }; } } public static async Task<TokenResponse> GenerateAccessToken(string appId, string appSecret, string userId) { using (HttpClient client = new HttpClient()) { var headers = GetNonceAuthorization(appId, appSecret); foreach (var header in headers) { client.DefaultRequestHeaders.Add(header.Key, header.Value); } var payload = new { userID = userId }; string jsonPayload = JsonConvert.SerializeObject(payload); var content = new StringContent(jsonPayload, Encoding.UTF8, "application/json"); HttpResponseMessage response = await client.PostAsync(LOGIN_TOKEN_URL, content); response.EnsureSuccessStatusCode(); string responseBody = await response.Content.ReadAsStringAsync(); TokenResponse result = JsonConvert.DeserializeObject<TokenResponse>(responseBody); return result; } } public static async Task Main(string[] args) { try { # 使用项目实际的 APP_ID, APP_SECRET, 以及 玩家 id 获取包含access_token的返回 TokenResponse result = await GenerateAccessToken(APP_ID, APP_SECRET, USER_ID); } catch (Exception ex) { Console.WriteLine($"\n 获取 Token 失败: {ex.Message}"); } } }JavaScript// JavaScript 使用示例 - Nonce 授权方式 const axios = require('axios'); const crypto = require('crypto'); const LOGIN_TOKEN_URL = "https://p.unity.cn/v1/login/token"; function getNonceAuthorization(appId, appSecret) { const nonce = crypto.randomUUID(); const timestamp = Math.floor(Date.now() / 1000); const signString = `${appId}:${appSecret}:${timestamp}:${nonce}`; const hash = crypto.createHash('sha256').update(signString).digest('hex'); return { 'X-APPID': appId, 'X-TIMESTAMP': timestamp.toString(), 'X-NONCE': nonce, 'Authorization': `nonce ${hash}` }; } async function generateAccessToken(appId, appSecret, userId) { const payload = { userID: userId }; const headers = getNonceAuthorization(appId, appSecret); const response = await axios.post(LOGIN_TOKEN_URL, payload, { headers }); return response.data; } async function main() { // 使用项目实际的 APP_ID, APP_SECRET, 以及 玩家 id 获取包含access_token的返回 const result = await generateAccessToken(APP_ID, APP_SECRET, USER_ID); } if (require.main === module) { main(); }Python# Python 使用示例 - Nonce 授权方式 import hashlib import time import uuid import binascii import requests LOGIN_TOKEN_URL = "https://p.unity.cn/v1/login/token" def get_nonce_authorization(app_id, app_secret): """生成 nonce 授权 Headers""" nonce = str(uuid.uuid4()) timestamp = int(time.time()) calculated_check_sum = hashlib.sha256(f'{app_id}:{app_secret}:{timestamp}:{nonce}'.encode()).digest() token = binascii.hexlify(calculated_check_sum).decode() headers = { 'X-APPID': app_id, 'X-TIMESTAMP': str(timestamp), 'X-NONCE': nonce, 'Authorization': f'nonce {token}', 'Content-Type': 'application/json' } return headers def generate_access_token(app_id, app_secret, user_id): payload = {"userID": user_id} headers = get_nonce_authorization(app_id, app_secret) response = requests.post(LOGIN_TOKEN_URL, headers=headers, json=payload) response.raise_for_status() result = response.json() return result def main(): # 使用项目实际的 APP_ID, APP_SECRET, 以及 玩家 id 获取包含access_token的返回 result = generate_access_token(APP_ID, APP_SECRET, USER_ID) if __name__ == "__main__": main()
Basic Authorization (推荐服务器端程序使用)
使用 Basic Authorization 来获取 JWT。步骤如下:
在 UOS 网站上获取当前需要使用的 UOS APP 的 AppId 和 AppServiceSecret
注:此处 AppId 和 AppServiceSecret,可在 UOS 网站上获取


在请求 JWT API 的 Request Header 中增加如下 Header:
- Basic base64(appId:appServiceSecret)
完整获取 JWT 参考示例如下:
C## C# 使用示例 - Basic 授权方式 using System; using System.Net.Http; using System.Text; using System.Threading.Tasks; using Newtonsoft.Json; class Program { private const string LOGIN_TOKEN_URL = "https://p.unity.cn/v1/login/token"; public class TokenResponse { public string accessToken { get; set; } public object expiresAt { get; set; } } private static string GetBasicAuthorization(string appId, string appServiceSecret) { string credentials = $"{appId}:{appServiceSecret}"; byte[] credentialsBytes = Encoding.UTF8.GetBytes(credentials); string encodedCredentials = Convert.ToBase64String(credentialsBytes); return $"Basic {encodedCredentials}"; } public static async Task<TokenResponse> GenerateAccessToken(string appId, string appServiceSecret, string userId) { using (HttpClient client = new HttpClient()) { client.DefaultRequestHeaders.Add("Authorization", GetBasicAuthorization(appId, appServiceSecret)); var payload = new { userID = userId }; string jsonPayload = JsonConvert.SerializeObject(payload); var content = new StringContent(jsonPayload, Encoding.UTF8, "application/json"); HttpResponseMessage response = await client.PostAsync(LOGIN_TOKEN_URL, content); response.EnsureSuccessStatusCode(); string responseBody = await response.Content.ReadAsStringAsync(); TokenResponse result = JsonConvert.DeserializeObject<TokenResponse>(responseBody); return result; } } private static async Task Main(string[] args) { # 使用项目实际的 APP_ID, APP_SERVICE_SECRET, 以及 玩家 id 获取包含access_token的返回 TokenResponse result = await GenerateAccessToken(APP_ID, APP_SERVICE_SECRET, USER_ID); } }JavaScript// JavaScript 使用示例 - Basic 授权方式 const axios = require('axios'); const LOGIN_TOKEN_URL = "https://p.unity.cn/v1/login/token"; function getBasicAuthorization(appId, appServiceSecret) { /** 获取 basic auth 的 Header */ const credentials = `${appId}:${appServiceSecret}`; const encodedCredentials = btoa(credentials); return { 'Authorization': `Basic ${encodedCredentials}` }; } async function generateAccessToken(appId, appServiceSecret, userId) { const payload = { userID: userId }; const headers = getBasicAuthorization(appId, appServiceSecret); const response = await axios.post(LOGIN_TOKEN_URL, payload, { headers }); return response.data; } async function main() { // 使用项目实际的 APP_ID, APP_SERVICE_SECRET, 以及 玩家 id 获取包含access_token的返回 const result = await generateAccessToken(APP_ID, APP_SERVICE_SECRET, USER_ID); } if (require.main === module) { main(); }Python# Python 使用示例 - Basic 授权方式 import base64 import requests LOGIN_TOKEN_URL = "https://p.unity.cn/v1/login/token" def get_basic_authorization(app_id, app_service_secret): """ 获取basic auth的Header """ credentials = f'{app_id}:{app_service_secret}' encoded_credentials = base64.b64encode(credentials.encode("utf-8")).decode("utf-8") return {'Authorization': f'Basic {encoded_credentials}'} def generate_access_token(app_id, app_service_secret, user_id): payload = {"userID": user_id} headers = get_basic_authorization(app_id, app_service_secret) response = requests.post(LOGIN_TOKEN_URL, headers=headers, json=payload) response.raise_for_status() result = response.json() return result def main(): # 使用项目实际的 APP_ID, APP_SERVICE_SECRET, 以及 玩家 id 获取包含access_token的返回 result = generate_access_token(APP_ID, APP_SERVICE_SECRET, USER_ID) if __name__ == "__main__": main()Go// Go 使用示例 - Basic 授权方式 import ( "bytes" "encoding/base64" "encoding/json" "fmt" "io" "net/http" ) const LOGIN_TOKEN_URL = "https://p.unity.cn/v1/login/token" type TokenResponse struct { AccessToken string `json:"accessToken"` ExpiresAt interface{} `json:"expiresAt"` } func GetBasicAuthorization(appID, appServiceSecret string) string { credentials := appID + ":" + appServiceSecret encodedCredentials := base64.StdEncoding.EncodeToString([]byte(credentials)) return "Basic " + encodedCredentials } func GenerateAccessToken(appID, appServiceSecret, userID string) (*TokenResponse, error) { payload := map[string]string{ "userID": userID, } payloadBytes, err := json.Marshal(payload) if err != nil { return nil, fmt.Errorf("marshal payload error: %v", err) } req, err := http.NewRequest("POST", LOGIN_TOKEN_URL, bytes.NewBuffer(payloadBytes)) if err != nil { return nil, fmt.Errorf("create request error: %v", err) } req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", GetBasicAuthorization(appID, appServiceSecret)) client := &http.Client{} resp, err := client.Do(req) if err != nil { return nil, fmt.Errorf("request error: %v", err) } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("read response error: %v", err) } if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("request failed with status %d: %s", resp.StatusCode, string(body)) } var result TokenResponse if err := json.Unmarshal(body, &result); err != nil { return nil, fmt.Errorf("unmarshal response error: %v", err) } return &result, nil } func main() { // 使用项目实际的 APP_ID, APP_SERVICE_SECRET, 以及 玩家 id 获取包含access_token的返回 result, err := GenerateAccessToken(APP_ID, APP_SERVICE_SECRET, USER_ID) if err != nil { fmt.Printf("\n 获取 Token 失败: %v\n", err) return } }
:::
鉴权
获取用户的JWT身份认证信息后,在后续请求下列客户 API 的 Request Header 中增加如下 Header:
- Authorization: Bearer {jwt}
- X-TIMESTAMP: 当前时间戳
- X-NONCE: 随机生成的UUID
- X-APPID: 当前需要使用的 UOS APP 的 AppId
- X-NONCE-TOKEN: hexadecimal(sha256(appId + ":" + appSecret + ":" + X-TIMESTAMP + ":" + X-NONCE))
"Nonce Authorization"是一种身份验证机制,用于确保在通信中的请求是合法的。"Nonce" 是 "Number Used Once"(一次性数字)的缩写,是一个只能使用一次的随机或唯一值。Nonce 在身份验证中通常用于防止重放攻击。
C#
// C# 使用示例
using System.Security.Cryptography;
using System.Text;
class Program
{
static Dictionary<string, string> SetNonce(string appId, string appSecret, string jwt)
{
string nonce = Guid.NewGuid().ToString();
long timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
string signString = $"{appId}:{appSecret}:{timestamp}:{nonce}";
string token = BitConverter.ToString(SHA256.HashData(Encoding.UTF8.GetBytes(signString))).Replace("-", "").ToLower();
return new Dictionary<string, string>
{
{ "Authorization", $"Bearer {jwt}" },
{ "Content-Type", "application/json" },
{ "X-TIMESTAMP", timestamp.ToString() },
{ "X-NONCE", nonce },
{ "X-NONCE-TOKEN", token },
{ "X-APPID", appId }
};
}
static async Task Main(string[] args)
{
// 替换 jwt 为获取到的 jwt
var headers = SetNonce(appId, appSecret, jwt);
using var httpClient = new HttpClient();
foreach (var header in headers)
{
httpClient.DefaultRequestHeaders.TryAddWithoutValidation(header.Key, header.Value);
}
// 替换 url 为你需要请求的 url
var response = await httpClient.GetAsync(url);
var content = await response.Content.ReadAsStringAsync();
Console.WriteLine(content);
}
}Python
# Python 使用示例
import time
import uuid
import hashlib
import requests
def set_nonce(app_id, app_secret, jwt):
nonce = str(uuid.uuid4())
timestamp = int(time.time())
sign_string = f"{app_id}:{app_secret}:{timestamp}:{nonce}"
token = hashlib.sha256(sign_string.encode("utf-8")).hexdigest()
return {
"Authorization": f"Bearer {jwt}",
"Content-Type": "application/json",
"X-TIMESTAMP": str(timestamp),
"X-NONCE": nonce,
"X-NONCE-TOKEN": token,
"X-APPID": app_id
}
# 替换 jwt 为获取到的 jwt
headers = set_nonce(app_id, app_secret, jwt)
# 替换 url 为你需要请求的 url
response = requests.get(url, headers=headers)JavaScript
// JavaScript 使用示例
const axios = require('axios');
const crypto = require('crypto');
function setNonce(appId, appSecret, jwt) {
const nonce = crypto.randomUUID();
const timestamp = Math.floor(Date.now() / 1000);
const signString = `${appId}:${appSecret}:${timestamp}:${nonce}`;
const token = crypto.createHash('sha256').update(signString, 'utf8').digest('hex');
return {
'Authorization': `Bearer ${jwt}`,
'Content-Type': 'application/json',
'X-TIMESTAMP': String(timestamp),
'X-NONCE': nonce,
'X-NONCE-TOKEN': token,
'X-APPID': appId
};
}
// 替换 jwt 为获取到的 jwt
const headers = setNonce(appId, appSecret, jwt);
// 替换 url 为你需要请求的 url
const response = await axios.get(url, { headers });