You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过服务账号生成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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 04:25:09