killapp/Assets/Scripts/Managers/FirebaseAuthManager.cs
2026-06-18 09:38:23 +08:00

401 lines
13 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using AppleAuth;
using AppleAuth.Enums;
using AppleAuth.Interfaces;
using AppleAuth.Native;
using Kill.Utils;
using UnityEngine;
#if (UNITY_ANDROID || UNITY_IOS) && !UNITY_EDITOR
using Firebase;
using Firebase.Auth;
#endif
namespace Kill.Managers
{
/// <summary>
/// Firebase 第三方登录管理器(支持 Google / Apple
/// </summary>
public class FirebaseAuthManager : MonoBehaviour
{
public static FirebaseAuthManager Instance { get; private set; }
// Google 登录配置 - 从 Firebase 控制台获取的 Web 客户端 ID
private const string WEB_CLIENT_ID = "785438724947-kpjbqi43hbj6eddianbjsgkgkkclkfmd.apps.googleusercontent.com";
#if (UNITY_ANDROID || UNITY_IOS) && !UNITY_EDITOR
private FirebaseAuth auth;
#endif
private AppleAuthManager appleAuthManager;
private void Awake()
{
Instance = this;
InitializeFirebase();
InitializeAppleAuth();
}
private void Update()
{
appleAuthManager?.Update();
}
/// <summary>
/// 初始化 Firebase
/// </summary>
private void InitializeFirebase()
{
#if (UNITY_ANDROID || UNITY_IOS) && !UNITY_EDITOR
FirebaseApp.CheckAndFixDependenciesAsync().ContinueWith(task =>
{
var dependencyStatus = task.Result;
if (dependencyStatus == DependencyStatus.Available)
{
auth = FirebaseAuth.DefaultInstance;
Debug.Log("Firebase 初始化成功");
}
else
{
Debug.LogError($"Firebase 初始化失败: {dependencyStatus}");
}
});
#else
Debug.Log("Firebase 第三方登录仅在 Android / iOS 平台可用");
#endif
}
/// <summary>
/// 初始化 Apple Sign In
/// </summary>
private void InitializeAppleAuth()
{
if (AppleAuthManager.IsCurrentPlatformSupported)
{
appleAuthManager = new AppleAuthManager(new PayloadDeserializer());
Debug.Log("Apple Sign In 初始化成功");
}
else
{
Debug.Log("当前平台不支持 Apple Sign In");
}
}
/// <summary>
/// 谷歌登录
/// </summary>
public void SignInWithGoogle(Action<GoogleLoginResult> onSuccess, Action<string> onError, int timeoutSeconds = 30)
{
SignInWithProvider("google.com", new string[] { "email", "profile" }, onSuccess, onError, timeoutSeconds);
}
/// <summary>
/// 苹果登录(使用 apple-signin-unity + Firebase OAuth Credential
/// </summary>
public void SignInWithApple(Action<GoogleLoginResult> onSuccess, Action<string> onError, int timeoutSeconds = 30)
{
#if UNITY_IOS && !UNITY_EDITOR
if (appleAuthManager == null)
{
onError?.Invoke("Apple Sign In 未初始化");
return;
}
if (!AppleAuthManager.IsCurrentPlatformSupported)
{
onError?.Invoke("当前平台不支持苹果登录");
return;
}
var rawNonce = GenerateRandomString(32);
var nonce = GenerateSHA256NonceFromRawNonce(rawNonce);
var loginArgs = new AppleAuthLoginArgs(
LoginOptions.IncludeEmail | LoginOptions.IncludeFullName,
nonce);
appleAuthManager.LoginWithAppleId(
loginArgs,
credential =>
{
var appleIdCredential = credential as IAppleIDCredential;
if (appleIdCredential == null)
{
MainThread.Enqueue(() => onError?.Invoke("未获取到 Apple ID 凭证"));
return;
}
var identityToken = Encoding.UTF8.GetString(appleIdCredential.IdentityToken);
var authorizationCode = Encoding.UTF8.GetString(appleIdCredential.AuthorizationCode);
if (string.IsNullOrEmpty(identityToken))
{
MainThread.Enqueue(() => onError?.Invoke("Apple identity token 为空"));
return;
}
SignInWithAppleCredential(appleIdCredential, identityToken, authorizationCode, rawNonce, onSuccess, onError);
},
error =>
{
MainThread.Enqueue(() => onError?.Invoke($"Apple 登录失败: {error.LocalizedDescription}"));
});
#else
onError?.Invoke("苹果登录仅在 iOS 平台可用");
#endif
}
#if UNITY_IOS && !UNITY_EDITOR
/// <summary>
/// 使用 Apple 凭证完成 Firebase 登录
/// </summary>
private void SignInWithAppleCredential(
IAppleIDCredential appleIdCredential,
string identityToken,
string authorizationCode,
string rawNonce,
Action<GoogleLoginResult> onSuccess,
Action<string> onError)
{
if (auth == null)
{
onError?.Invoke("Firebase 未初始化");
return;
}
var firebaseCredential = OAuthProvider.GetCredential(
"apple.com",
identityToken,
rawNonce,
authorizationCode);
auth.SignInWithCredentialAsync(firebaseCredential).ContinueWith(task =>
{
if (task.IsCanceled)
{
MainThread.Enqueue(() => onError?.Invoke("Firebase 登录被取消"));
return;
}
if (task.IsFaulted)
{
MainThread.Enqueue(() => onError?.Invoke("Firebase 登录失败"));
return;
}
var user = auth.CurrentUser;
if (user == null)
{
MainThread.Enqueue(() => onError?.Invoke("获取 Firebase 用户信息失败"));
return;
}
MainThread.Enqueue(() =>
{
var result = new GoogleLoginResult
{
FirebaseUserId = user.UserId,
Email = user.Email ?? appleIdCredential.Email,
DisplayName = user.DisplayName ?? GetAppleDisplayName(appleIdCredential.FullName),
PhotoUrl = user.PhotoUrl?.ToString(),
IdToken = ""
};
onSuccess?.Invoke(result);
});
});
}
#endif
/// <summary>
/// 通用 OAuth 登录Google
/// </summary>
private void SignInWithProvider(string providerId, string[] scopes, Action<GoogleLoginResult> onSuccess, Action<string> onError, int timeoutSeconds)
{
#if UNITY_ANDROID && !UNITY_EDITOR
if (auth == null)
{
onError?.Invoke("Firebase 未初始化");
return;
}
bool isCompleted = false;
// 启动超时检测
Task.Run(async () =>
{
await Task.Delay(TimeSpan.FromSeconds(timeoutSeconds));
if (!isCompleted)
{
isCompleted = true;
MainThread.Enqueue(() => onError?.Invoke($"{providerId} 登录超时({timeoutSeconds}秒)"));
}
});
var provider = new FederatedOAuthProviderData(providerId);
provider.Scopes = scopes;
var federatedProvider = new FederatedOAuthProvider(provider);
auth.SignInWithProviderAsync(federatedProvider).ContinueWith(task =>
{
if (isCompleted) return; // 已超时,忽略结果
isCompleted = true;
if (task.IsFaulted)
{
MainThread.Enqueue(() => onError?.Invoke($"{providerId} 登录失败"));
return;
}
if (task.IsCanceled)
{
MainThread.Enqueue(() => onError?.Invoke($"{providerId} 登录被取消"));
return;
}
var user = auth.CurrentUser;
if (user == null)
{
MainThread.Enqueue(() => onError?.Invoke("获取用户信息失败"));
return;
}
// 构造登录结果
MainThread.Enqueue(() =>
{
var result = new GoogleLoginResult
{
FirebaseUserId = user.UserId,
Email = user.Email,
DisplayName = user.DisplayName,
PhotoUrl = user.PhotoUrl?.ToString(),
IdToken = ""
};
onSuccess?.Invoke(result);
});
});
#else
// 编辑器 / iOS 下谷歌登录不可用
Task.Delay(1000).ContinueWith(_ =>
{
Debug.Log($"编辑器模拟 {providerId} 登录");
MainThread.Enqueue(() =>
{
onError?.Invoke($"{providerId} 登录在当前平台不可用");
});
});
#endif
}
/// <summary>
/// 登出
/// </summary>
public void SignOut()
{
#if (UNITY_ANDROID || UNITY_IOS) && !UNITY_EDITOR
auth?.SignOut();
Debug.Log("Firebase 登出成功");
#endif
}
#region Nonce
/// <summary>
/// 生成随机字符串作为 raw nonce
/// </summary>
private static string GenerateRandomString(int length)
{
if (length <= 0)
{
throw new Exception("Expected nonce to have positive length");
}
const string charset = "0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._";
using (var rng = new RNGCryptoServiceProvider())
{
var result = new StringBuilder();
var randomNumberHolder = new byte[1];
while (result.Length < length)
{
var randomNumbers = new List<int>(16);
for (var i = 0; i < 16; i++)
{
rng.GetBytes(randomNumberHolder);
randomNumbers.Add(randomNumberHolder[0]);
}
foreach (var randomNumber in randomNumbers)
{
if (result.Length >= length)
break;
if (randomNumber < charset.Length)
{
result.Append(charset[randomNumber]);
}
}
}
return result.ToString();
}
}
/// <summary>
/// 计算 raw nonce 的 SHA256 值,传给 Apple
/// </summary>
private static string GenerateSHA256NonceFromRawNonce(string rawNonce)
{
using (var sha = SHA256.Create())
{
var hash = sha.ComputeHash(Encoding.UTF8.GetBytes(rawNonce));
var result = new StringBuilder();
for (var i = 0; i < hash.Length; i++)
{
result.Append(hash[i].ToString("x2"));
}
return result.ToString();
}
}
#endregion
#region
/// <summary>
/// 从 Apple 全名构造显示名称
/// </summary>
private static string GetAppleDisplayName(IPersonName fullName)
{
if (fullName == null)
return null;
var parts = new List<string>();
if (!string.IsNullOrEmpty(fullName.GivenName))
parts.Add(fullName.GivenName);
if (!string.IsNullOrEmpty(fullName.FamilyName))
parts.Add(fullName.FamilyName);
return parts.Count > 0 ? string.Join(" ", parts) : null;
}
#endregion
}
/// <summary>
/// 第三方登录结果(兼容 Google / Apple
/// </summary>
public class GoogleLoginResult
{
public string FirebaseUserId; // Firebase 用户ID用于绑定
public string Email; // 邮箱
public string DisplayName; // 显示名称
public string PhotoUrl; // 头像URL
public string IdToken; // ID Token
}
}