Unity中如何借助共享Apple Keychain实现Firebase跨应用认证?
在Unity中借助共享Apple Keychain实现Firebase跨应用认证的方案
前提条件
- 所有需要共享认证状态的App必须归属同一Apple开发者账号,且已在Apple开发者后台为各App ID配置相同的Keychain Group(格式通常为
com.yourcompany.keychaingroup) - Unity项目已集成Firebase Auth SDK,且iOS平台基础配置完成
- 使用的Xcode版本符合Firebase与Unity的最低要求
步骤1:配置iOS端Keychain共享
你可以选择手动处理导出的Xcode项目,或通过Unity的PostProcessBuild脚本自动配置:
方式1:手动配置Xcode项目
- 将Unity项目导出为Xcode工程并打开
- 进入项目的
Signing & Capabilities标签,点击+ Capability添加Keychain Sharing - 在Keychain Groups列表中添加你在开发者后台配置的Group ID
方式2:Unity自动配置脚本
在Unity项目的Assets/Editor目录下创建以下脚本,实现导出Xcode项目时自动添加Keychain共享能力:
using UnityEditor; using UnityEditor.Callbacks; using UnityEditor.iOS.Xcode; using System.IO; public class KeychainPostBuildHandler { [PostProcessBuild(999)] public static void OnPostProcessBuild(BuildTarget target, string buildPath) { if (target != BuildTarget.iOS) return; // 编辑PBXProject string pbxPath = PBXProject.GetPBXProjectPath(buildPath); PBXProject pbxProject = new PBXProject(); pbxProject.ReadFromFile(pbxPath); string targetGuid = pbxProject.GetUnityFrameworkTargetGuid(); // 添加Keychain Sharing能力 pbxProject.AddCapability(targetGuid, PBXCapabilityType.KeychainSharing); // 替换为你的Keychain Group ID pbxProject.AddKeychainGroup(targetGuid, "com.yourcompany.keychaingroup"); pbxProject.WriteToFile(pbxPath); // 编辑Info.plist(添加必要权限说明) string plistPath = Path.Combine(buildPath, "Info.plist"); PlistDocument plist = new PlistDocument(); plist.ReadFromFile(plistPath); plist.root.SetString("NSUserTrackingUsageDescription", "用于跨应用共享Apple登录状态"); plist.WriteToFile(plistPath); } }
步骤2:实现Keychain凭证读写的原生桥接
在Unity项目的Assets/Plugins/iOS目录下创建以下Objective-C文件,提供Unity与iOS原生Keychain操作的接口:
#import <Foundation/Foundation.h> #import <Security/Security.h> @interface KeychainHelper : NSObject + (BOOL)saveAppleAuthData:(NSString *)userID identityToken:(NSString *)token toGroup:(NSString *)group; + (NSDictionary *)getAppleAuthDataFromGroup:(NSString *)group; @end @implementation KeychainHelper + (BOOL)saveAppleAuthData:(NSString *)userID identityToken:(NSString *)token toGroup:(NSString *)group { NSDictionary *query = @{ (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword, (__bridge id)kSecAttrAccount: @"FirebaseAppleAuth", (__bridge id)kSecAttrService: @"CrossAppAuth", (__bridge id)kSecAttrAccessGroup: group, (__bridge id)kSecValueData: [token dataUsingEncoding:NSUTF8StringEncoding], (__bridge id)kSecAttrGeneric: [userID dataUsingEncoding:NSUTF8StringEncoding] }; // 先删除旧凭证 SecItemDelete((__bridge CFDictionaryRef)query); // 添加新凭证 OSStatus status = SecItemAdd((__bridge CFDictionaryRef)query, NULL); return status == errSecSuccess; } + (NSDictionary *)getAppleAuthDataFromGroup:(NSString *)group { NSDictionary *query = @{ (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword, (__bridge id)kSecAttrAccount: @"FirebaseAppleAuth", (__bridge id)kSecAttrService: @"CrossAppAuth", (__bridge id)kSecAttrAccessGroup: group, (__bridge id)kSecReturnData: (__bridge id)kCFBooleanTrue, (__bridge id)kSecReturnAttributes: (__bridge id)kCFBooleanTrue, (__bridge id)kSecMatchLimit: (__bridge id)kSecMatchLimitOne }; CFTypeRef result = NULL; OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &result); if (status == errSecSuccess) { NSDictionary *item = (__bridge NSDictionary *)result; NSString *token = [[NSString alloc] initWithData:item[(__bridge id)kSecValueData] encoding:NSUTF8StringEncoding]; NSString *userID = [[NSString alloc] initWithData:item[(__bridge id)kSecAttrGeneric] encoding:NSUTF8StringEncoding]; CFRelease(result); return @{@"userID": userID, @"token": token}; } else { CFRelease(result); return nil; } } @end // Unity C#调用接口 extern "C" { bool _SaveAppleAuthData(const char* userID, const char* token, const char* group) { return [KeychainHelper saveAppleAuthData:[NSString stringWithUTF8String:userID] identityToken:[NSString stringWithUTF8String:token] toGroup:[NSString stringWithUTF8String:group]]; } const char* _GetAppleAuthData(const char* group) { NSDictionary *data = [KeychainHelper getAppleAuthDataFromGroup:[NSString stringWithUTF8String:group]]; if (data) { NSString *json = [[NSString alloc] initWithData:[NSJSONSerialization dataWithJSONObject:data options:0 error:nil] encoding:NSUTF8StringEncoding]; return strdup([json UTF8String]); } return NULL; } }
步骤3:Unity端Firebase认证逻辑
在Unity中编写C#脚本,实现Apple登录后的凭证存储,以及跨应用的Firebase自动登录:
using Firebase.Auth; using System.Runtime.InteropServices; using UnityEngine; using System.Threading.Tasks; public class CrossAppFirebaseAuth : MonoBehaviour { [DllImport("__Internal")] private static extern bool _SaveAppleAuthData(string userID, string token, string group); [DllImport("__Internal")] private static extern string _GetAppleAuthData(string group); private const string KeychainGroup = "com.yourcompany.keychaingroup"; // 第一个App中Apple登录成功后调用 public async Task HandleAppleLoginSuccess(string appleUserID, string identityToken) { // 保存凭证到共享Keychain bool saveSuccess = _SaveAppleAuthData(appleUserID, identityToken, KeychainGroup); if (!saveSuccess) { Debug.LogError("Keychain凭证保存失败"); return; } // 登录Firebase var credential = OAuthProvider.GetCredential("apple.com", identityToken); await FirebaseAuth.DefaultInstance.SignInWithCredentialAsync(credential); Debug.Log("Firebase登录成功"); } // 第二个App启动时调用,尝试跨应用登录 public async Task TryCrossAppLogin() { string credentialJson = _GetAppleAuthData(KeychainGroup); if (string.IsNullOrEmpty(credentialJson)) { Debug.Log("Keychain中无可用认证凭证"); return; } var authData = JsonUtility.FromJson<AppleAuthData>(credentialJson); var firebaseCredential = OAuthProvider.GetCredential("apple.com", authData.token); await FirebaseAuth.DefaultInstance.SignInWithCredentialAsync(firebaseCredential); Debug.Log("跨应用Firebase登录成功"); } [System.Serializable] private class AppleAuthData { public string userID; public string token; } }
关键注意事项
- 所有App的Bundle ID必须在同一Apple开发者账号下,且Keychain Group完全一致
- Apple登录仅支持iOS 13及以上版本,需处理低版本兼容逻辑
- Firebase的OAuth凭证存在有效期,若登录失败需引导用户重新进行Apple登录
- Keychain中存储的凭证建议加密处理,示例中使用的是Generic Password类型,可根据需求调整存储策略
内容的提问来源于stack exchange,提问作者AJS
相关产品推荐
相关产品推荐

