使用Google.Apis.Auth.AspNetCore3调用Gmail API时Token过期或被吊销问题求助
问题成因
- 核心原因是你的OpenID Connect配置未显式要求返回刷新令牌:Google OAuth默认只返回有效期1小时的访问令牌(access_token),只有显式设置
access_type=offline才会返回可用于刷新访问令牌的刷新令牌(refresh_token),你当前配置缺少该参数,拿到的refresh_token大概率为空或无效,自然无法完成令牌刷新。 - 令牌未持久化更新:你每次调用Gmail API时都从Cookie中读取首次登录时存储的旧令牌,即便某次刷新成功,新的令牌也没有更新回Cookie的身份认证属性中,下一次调用依然会使用已过期的旧令牌,导致报错。
- 测试应用令牌有效期限制:如果你的Google Cloud应用处于测试状态,刷新令牌默认有效期只有7天,不过你1小时就触发报错,说明核心问题还是前两点。
修复方案
步骤1:修改Startup.cs中的OAuth配置
新增三个必要配置项,确保能拿到有效刷新令牌且令牌会被自动存入Cookie:
services.AddAuthentication(options => { options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; }) .AddCookie() .AddGoogleOpenIdConnect(options => { var secrets = GoogleClientSecrets.FromFile("google_client_secret.json").Secrets; options.Scope.Add(GmailService.Scope.GmailSend); options.Scope.Add(GmailService.Scope.GmailSettingsSharing); options.Scope.Add(GmailService.Scope.GmailSettingsBasic); options.ClientId = secrets.ClientId; options.ClientSecret = secrets.ClientSecret; options.CallbackPath = new PathString("/signin-google"); // 新增以下3行配置 options.AccessType = "offline"; options.SaveTokens = true; options.Prompt = "consent"; });
步骤2:优化Gmail API调用逻辑,主动更新Cookie中的令牌
在调用API前先判断访问令牌是否即将过期,刷新成功后将新令牌写回Cookie,避免后续调用使用旧令牌:
AuthenticateResult authResult = await HttpContext.AuthenticateAsync(); if (!authResult.Succeeded) { return Challenge(GoogleOpenIdConnectDefaults.AuthenticationScheme); } string accessToken = authResult.Properties.GetTokenValue(OpenIdConnectParameterNames.AccessToken); string refreshToken = authResult.Properties.GetTokenValue(OpenIdConnectParameterNames.RefreshToken); string expiresAtStr = authResult.Properties.GetTokenValue("expires_at"); DateTimeOffset expiresAt = DateTimeOffset.Parse(expiresAtStr); var secrets = GoogleClientSecrets.FromFile("google_client_secret.json").Secrets; string googleAuthTokenPath = "GoogleAuthToken"; // 提前5分钟判断令牌是否即将过期,主动刷新 if (expiresAt <= DateTimeOffset.UtcNow.AddMinutes(5)) { var flow = new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer { ClientSecrets = secrets, DataStore = new FileDataStore(googleAuthTokenPath, true), }); // 刷新获取新令牌 var newToken = await flow.RefreshTokenAsync("user", refreshToken, CancellationToken.None); // 更新Cookie中存储的令牌信息 authResult.Properties.UpdateTokenValue(OpenIdConnectParameterNames.AccessToken, newToken.AccessToken); authResult.Properties.UpdateTokenValue("expires_at", DateTimeOffset.UtcNow.AddSeconds(newToken.ExpiresInSeconds.Value).ToString("o")); if (!string.IsNullOrEmpty(newToken.RefreshToken)) { authResult.Properties.UpdateTokenValue(OpenIdConnectParameterNames.RefreshToken, newToken.RefreshToken); } // 重新签入更新Cookie await HttpContext.SignInAsync(authResult.Principal, authResult.Properties); // 更新当前上下文使用的令牌 accessToken = newToken.AccessToken; refreshToken = newToken.RefreshToken ?? refreshToken; } var credentials = new UserCredential(new GoogleAuthorizationCodeFlow( new GoogleAuthorizationCodeFlow.Initializer { ClientSecrets = secrets, DataStore = new FileDataStore(googleAuthTokenPath, true), }), "user", new TokenResponse { AccessToken = accessToken, RefreshToken = refreshToken } ); var service = new GmailService(new BaseClientService.Initializer() { HttpClientInitializer = credentials, ApplicationName = "Some app", });
额外注意事项
- 如果你的Google Cloud应用目前处于测试状态,需在OAuth同意屏幕页面将应用发布为正式版,否则刷新令牌7天就会过期失效。
- 敏感Gmail Scope需要在Google Cloud控制台完成OAuth应用验证,未验证的应用会有功能和有效期限制。
- 不要频繁调用刷新接口,短时间内多次使用同一个刷新令牌请求新令牌可能触发Google的安全机制,导致刷新令牌被撤回。
内容的提问来源于stack exchange,提问作者dasisderblyme
相关产品推荐
相关产品推荐

