C# .NET WPF连接Google Cloud Firestore时自定义令牌偶发认证错误
问题背景与现象
我们的C# .NET WPF应用通过自定义令牌连接Google Cloud Firestore,令牌由同服务器的Node.js Firebase Function生成,令牌内嵌元数据用于Firestore Rules权限控制。该功能稳定运行18个月后,近一周出现偶发的Unauthenticated错误:
- 读写操作时约40%概率触发错误,仅发生在
FirestoreDb执行操作阶段(比如await batch.CommitAsync();),认证、令牌生成、FirestoreDb创建阶段无报错 - 错误信息固定:
Grpc.Core.RpcException: Status(StatusCode="Unauthenticated", Detail="Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential.") at Google.Cloud.Firestore.WriteBatch.<CommitAsync>d__15.MoveNext() in /_/apis/Google.Cloud.Firestore/Google.Cloud.Firestore/WriteBatch.cs:line 226 - 用户立即重试连接,约90%概率恢复正常,剩余10%仍报错
相关代码
C# 客户端代码
using Firebase.Auth; using Flurl.Http; using Google.Cloud.Firestore; using Google.Cloud.Firestore.V1; using Grpc.Core; dynamic response = await GetAuthenticationToken(requestToken); FirestoreDb db = CreateFirestoreDbWithToken(response.token, FIREBASE_WEB_API, FIREBASE_PROJECT_ID); private FirestoreDb CreateFirestoreDbWithToken(string token, string firebaseApiKey, string firebaseProjectId) { FirebaseAuthProvider authProvider = new FirebaseAuthProvider(new FirebaseConfig(firebaseApiKey)); firebaseAuth = authProvider.SignInWithCustomTokenAsync(token).Result; CallCredentials callCredentials = CallCredentials.FromInterceptor(async (context, metadata) => { if (firebaseAuth.IsExpired()) firebaseAuth = await firebaseAuth.GetFreshAuthAsync(); if (string.IsNullOrEmpty(firebaseAuth.FirebaseToken)) return; metadata.Clear(); metadata.Add("authorization", $"Bearer {firebaseAuth.FirebaseToken}"); }); ChannelCredentials credentials = ChannelCredentials.Create(new SslCredentials(), callCredentials); FirestoreClientBuilder builder = new FirestoreClientBuilder { ChannelCredentials = credentials }; return FirestoreDb.Create(firebaseProjectId, builder.Build()); } public async Task<dynamic> GetAuthenticationToken(string requestToken) { var functionPath = FIREBASE_FUNCTION_NAME + ".cloudfunctions.net/verifyClient"; var response = await functionPath.WithHeader("Content-Type", "application/json; charset=utf-8") .PostJsonAsync ( new { requestToken, } ) .ReceiveJson(); return response; } public async Task WriteData() { // ... WriteBatch batch = db.StartBatch(); batch.Set(dataRef, dataObject); await batch.CommitAsync(); // ... }
Node.js Firebase Function代码
const functions = require('firebase-functions'); const admin = require('firebase-admin'); admin.initializeApp(functions.config().firebase) exports.verifyClient = functions .https.onRequest((request, response) => { // ... 业务逻辑 ... return createAuthToken(requestEmail, groupID).then(authToken => { response.send({ result: "OK", token: authToken }); return; }) }); function createAuthToken(requestEmail, groupID) { return admin.auth() .createCustomToken(requestEmail, { adminID: groupID }) .then(customToken => { return customToken; }) .catch(err => { console.error('createAuthToken Error: ' + err); return ""; }); }
原因分析
- 令牌刷新竞态问题:C#代码中
firebaseAuth是共享实例,无线程安全控制,并发场景下多个请求同时触发令牌刷新时,可能出现某请求拿到未完全刷新的无效令牌。 - Grpc元数据处理缺陷:
metadata.Clear()会清空所有元数据(包括Grpc内部必要字段),可能导致Firestore服务端无法正确识别令牌格式。 - 服务端临时认证波动:近期Firestore认证服务可能存在偶发的令牌校验延迟或缓存不一致,导致有效令牌被误判为无效,重试可绕过。
解决方案
1. 修复线程安全的令牌刷新逻辑
为firebaseAuth添加线程锁,避免并发刷新冲突:
private readonly object _authLock = new object(); private FirebaseAuthLink firebaseAuth; private async Task<FirebaseAuthLink> GetValidAuthLink(string token, string firebaseApiKey) { lock (_authLock) { if (firebaseAuth != null && !firebaseAuth.IsExpired()) return firebaseAuth; } FirebaseAuthProvider authProvider = new FirebaseAuthProvider(new FirebaseConfig(firebaseApiKey)); var freshAuth = await authProvider.SignInWithCustomTokenAsync(token); lock (_authLock) { firebaseAuth = freshAuth; } return firebaseAuth; }
修改拦截器逻辑,调用上述方法获取有效令牌:
CallCredentials callCredentials = CallCredentials.FromInterceptor(async (context, metadata) => { var validAuth = await GetValidAuthLink(token, firebaseApiKey); if (string.IsNullOrEmpty(validAuth.FirebaseToken)) return; // 仅更新Authorization头,保留其他元数据 metadata.Remove("authorization"); metadata.Add("authorization", $"Bearer {validAuth.FirebaseToken}"); });
2. 添加操作重试机制
针对Unauthenticated错误自动重试,利用重试大概率成功的特性:
public async Task WriteDataWithRetry() { int retryCount = 3; while (retryCount > 0) { try { WriteBatch batch = db.StartBatch(); batch.Set(dataRef, dataObject); await batch.CommitAsync(); return; } catch (RpcException ex) when (ex.StatusCode == StatusCode.Unauthenticated && retryCount > 0) { retryCount--; // 重试前强制清空旧令牌,触发重新获取 lock (_authLock) { firebaseAuth = null; } await Task.Delay(100); } } throw new Exception("重试多次后仍无法完成Firestore操作"); }
3. 更新依赖库版本
确认Firebase.Auth和Google.Cloud.Firestore NuGet包为最新稳定版,修复依赖库中的已知认证bug。
内容的提问来源于stack exchange,提问作者Damian
相关产品推荐
相关产品推荐

