如何通过服务账号生成JWT实现Google API认证(Unity3D C#)
Unity C# 实现Google服务账号JWT签名并获取访问令牌
前置准备
- 确保Unity项目中已引入Newtonsoft.Json:可以通过Package Manager搜索导入,或者手动添加对应版本的dll文件
- 将服务账号的JSON密钥文件保存到Unity项目的
StreamingAssets目录下(没有这个目录就自己创建)
核心实现代码
创建一个名为GoogleJwtUtility的C#脚本,挂到任意GameObject上即可调用:
using System; using System.Collections.Generic; using System.Security.Cryptography; using System.Text; using UnityEngine; using UnityEngine.Networking; using Newtonsoft.Json; public class GoogleJwtUtility : MonoBehaviour { // 从StreamingAssets读取服务账号密钥文件 private string LoadServiceAccountKey() { string path = System.IO.Path.Combine(Application.streamingAssetsPath, "service-account-key.json"); #if UNITY_ANDROID && !UNITY_EDITOR using (UnityWebRequest www = UnityWebRequest.Get(path)) { www.SendWebRequest(); while (!www.isDone) {} if (www.result != UnityWebRequest.Result.Success) { Debug.LogError("加载密钥文件失败: " + www.error); return null; } return www.downloadHandler.text; } #else return System.IO.File.ReadAllText(path); #endif } // 调用此方法获取Google访问令牌 public async void GetGoogleAccessToken() { string keyJson = LoadServiceAccountKey(); if (string.IsNullOrEmpty(keyJson)) return; var serviceAccount = JsonConvert.DeserializeObject<ServiceAccount>(keyJson); // 1. 构造JWT Header var header = new Dictionary<string, string> { {"alg", "RS256"}, {"typ", "JWT"} }; string encodedHeader = Base64UrlEncode(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(header))); // 2. 构造JWT Payload long now = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); var payload = new Dictionary<string, object> { {"iss", serviceAccount.client_email}, {"scope", "https://www.googleapis.com/auth/cloud-platform"}, // 替换为你的API所需权限范围 {"aud", "https://oauth2.googleapis.com/token"}, {"exp", now + 3600}, // 有效期1小时,最大不能超过3600秒 {"iat", now} }; string encodedPayload = Base64UrlEncode(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(payload))); // 3. 生成签名 string signatureInput = $"{encodedHeader}.{encodedPayload}"; byte[] signatureBytes = SignData(Encoding.UTF8.GetBytes(signatureInput), serviceAccount.private_key); string encodedSignature = Base64UrlEncode(signatureBytes); // 4. 拼接完整JWT string jwt = $"{encodedHeader}.{encodedPayload}.{encodedSignature}"; // 5. 发送请求获取访问令牌 using (UnityWebRequest www = UnityWebRequest.Post("https://oauth2.googleapis.com/token", "")) { var formData = new WWWForm(); formData.AddField("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer"); formData.AddField("assertion", jwt); www.uploadHandler = new UploadHandlerRaw(formData.data); www.SetRequestHeader("Content-Type", "application/x-www-form-urlencoded"); var operation = www.SendWebRequest(); while (!operation.isDone) { await System.Threading.Tasks.Task.Delay(10); } if (www.result != UnityWebRequest.Result.Success) { Debug.LogError("获取令牌失败: " + www.downloadHandler.text); } else { var tokenResponse = JsonConvert.DeserializeObject<TokenResponse>(www.downloadHandler.text); Debug.Log("访问令牌获取成功: " + tokenResponse.access_token); // 拿到token后就可以调用对应的Google API了 } } } // RS256签名核心逻辑 private byte[] SignData(byte[] data, string privateKey) { // 清理私钥格式:移除首尾标记和换行符 string cleanKey = privateKey .Replace("-----BEGIN PRIVATE KEY-----", "") .Replace("-----END PRIVATE KEY-----", "") .Replace("\n", "") .Replace("\r", ""); byte[] keyBytes = Convert.FromBase64String(cleanKey); using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider()) { rsa.ImportPkcs8PrivateKey(keyBytes, out _); return rsa.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); } } // JWT专用的Base64URL编码(无填充、替换特殊字符) private string Base64UrlEncode(byte[] input) { string base64 = Convert.ToBase64String(input); return base64 .Replace('+', '-') .Replace('/', '_') .TrimEnd('='); } // 服务账号JSON密钥的结构映射 private class ServiceAccount { public string client_email { get; set; } public string private_key { get; set; } } // Google令牌响应的结构映射 private class TokenResponse { public string access_token { get; set; } public string token_type { get; set; } public int expires_in { get; set; } } }
关键注意事项
- 权限范围替换:把代码中的
scope值换成你要调用的Google API对应的权限,比如调用Google Sheets API就用https://www.googleapis.com/auth/spreadsheets - 私钥格式检查:一定要确保私钥的首尾标记和换行被正确清理,这是签名无效的常见原因
- 有效期限制:JWT的
exp字段必须设置为当前时间+3600秒以内,超出这个范围Google会直接拒绝 - 平台适配:代码已经处理了Android平台读取
StreamingAssets的特殊逻辑,Windows、iOS、Mac等平台直接可用 - 异步调用:示例中用
async/await避免阻塞Unity主线程,不要在Update或主线程同步方法中直接调用耗时的网络请求
内容的提问来源于stack exchange,提问作者filler003
相关产品推荐
相关产品推荐

