Google Auth返回的Token无法访问自研服务器API的问题求助
我开发了一套包含移动端和带API的Web服务器端的应用,使用Identity组件实现用户认证。移动端通过打开服务器端的WebView完成登录流程,用户登录成功后,服务器会返回Bearer token,移动端即可凭借该token访问需要Bearer认证的服务器API,该认证模式参考微软MAUI官方文档。
用户名密码登录时,移动端获取的Bearer token可正常访问API;但Google登录后,API返回401权限拒绝错误。
认证配置代码(Program.cs)
services.AddAuthentication(options => { options.DefaultAuthenticateScheme = IdentityConstants.ApplicationScheme; options.DefaultChallengeScheme = IdentityConstants.ApplicationScheme; options.DefaultSignInScheme = IdentityConstants.ApplicationScheme; }) // 之前尝试过.AddGoogle,但只有这种方式能返回id_token。这里的access_token是用于Google API的,而非内部API .AddOpenIdConnect( GoogleDefaults.AuthenticationScheme, GoogleDefaults.DisplayName, o => { var googleAuthNSection = configuration.GetSection("Authentication:Google"); o.SignInScheme = IdentityConstants.ExternalScheme; o.Authority = "https://accounts.google.com"; o.ClientId = googleAuthNSection["ClientId"]!; o.ClientSecret = googleAuthNSection["ClientSecret"]!; o.ResponseType = OpenIdConnectResponseType.IdToken; o.CallbackPath = "/signin-google"; o.SaveTokens = true; o.Scope.Add("email"); }) .AddCookie(IdentityConstants.ApplicationScheme, o => { o.LoginPath = new PathString("/Account/Login"); o.Events = new CookieAuthenticationEvents { OnValidatePrincipal = SecurityStampValidator.ValidatePrincipalAsync }; }) .AddBearerToken(IdentityConstants.BearerScheme, o => { o.BearerTokenExpiration = TimeSpan.FromHours(5); }) .AddExternalCookie();
用户名密码登录获取Bearer token的代码
var body = new { email, password }; var content = JsonSerializer.Serialize(body); var buffer = Encoding.UTF8.GetBytes(content); var byteContent = new ByteArrayContent(buffer); byteContent.Headers.ContentType = new MediaTypeHeaderValue("application/json"); var client = httpClientFactory.CreateClient(MealPlanApi.ApiClient); var loginResponse = await client.PostAsync("/login", byteContent); var contents = await loginResponse.Content.ReadAsStringAsync(); var doc = JsonSerializer.Deserialize<JsonElement>(contents); var accessToken = doc.GetProperty("accessToken").GetString()!; await RedirectToMobileAuthenticatedAsync(context, user, accessToken, returnUrl);
第三方登录返回Token给移动端的代码
[Route("mobileauth")] [ApiController] public class AuthController : ControllerBase { const string callbackScheme = "MyCallbackScheme"; [HttpGet("{scheme}")] // 例如: Microsoft, Facebook, Apple等 public async Task Get([FromRoute] string scheme) { var auth = await Request.HttpContext.AuthenticateAsync(scheme); if (!auth.Succeeded || auth?.Principal == null || !auth.Principal.Identities.Any(id => id.IsAuthenticated) || string.IsNullOrEmpty(auth.Properties.GetTokenValue("access_token"))) { // 未认证,发起挑战 await Request.HttpContext.ChallengeAsync(scheme); } else { var claims = auth.Principal.Identities.FirstOrDefault()?.Claims; var email = string.Empty; email = claims?.FirstOrDefault(c => c.Type == ClaimTypes.Email)?.Value; // 获取要发送回回调的参数 var qs = new Dictionary<string, string> { { "access_token", auth.Properties.GetTokenValue("access_token") }, { "refresh_token", auth.Properties.GetTokenValue("refresh_token") ?? string.Empty }, { "expires", (auth.Properties.ExpiresUtc?.ToUnixTimeSeconds() ?? -1).ToString() }, { "email", email } }; // 构建结果URL var url = callbackScheme + "://#" + string.Join( "&", qs.Where(kvp => !string.IsNullOrEmpty(kvp.Value) && kvp.Value != "-1") .Select(kvp => $"{WebUtility.UrlEncode(kvp.Key)}={WebUtility.UrlEncode(kvp.Value)}")); // 重定向到最终URL Request.HttpContext.Response.Redirect(url); } } }
API控制器授权配置
Program.cs中:
app.MapControllers();
控制器代码:
[Route("api/[controller]")] [Authorize(AuthenticationSchemes = "Identity.Bearer,Identity.External,Google")] [ApiController] public class MyController() : ControllerBase
通过HttpContext.AuthenticateAsync("Google")确认用户已成功认证,Web端可正常访问资源,但将获取的access_token或id_token传给移动端后,无法用于API访问,请求排查问题原因。
核心原因
你返回给移动端的是Google的access_token/id_token,而非服务器自身生成的Identity.Bearer token。API配置的Google认证scheme仅适用于Web端的Cookie认证,不会验证第三方JWT作为Bearer凭证,因此移动端用Google token调用API会被拒绝。
解决步骤
1. 第三方登录后生成服务器自有Bearer token
修改第三方登录的回调逻辑,在用户通过Google认证后,关联或创建本地Identity用户,再生成服务器自有Bearer token返回给移动端,和用户名密码登录流程保持一致:
[Route("mobileauth")] [ApiController] public class AuthController : ControllerBase { const string callbackScheme = "MyCallbackScheme"; private readonly UserManager<IdentityUser> _userManager; private readonly SignInManager<IdentityUser> _signInManager; public AuthController(UserManager<IdentityUser> userManager, SignInManager<IdentityUser> signInManager) { _userManager = userManager; _signInManager = signInManager; } [HttpGet("{scheme}")] public async Task Get([FromRoute] string scheme) { var auth = await Request.HttpContext.AuthenticateAsync(scheme); if (!auth.Succeeded || auth?.Principal == null || !auth.Principal.Identities.Any(id => id.IsAuthenticated)) { await Request.HttpContext.ChallengeAsync(scheme); return; } var email = auth.Principal.FindFirstValue(ClaimTypes.Email); if (string.IsNullOrEmpty(email)) { await Request.HttpContext.ChallengeAsync(scheme); return; } // 查找或创建本地用户 var user = await _userManager.FindByEmailAsync(email); if (user == null) { user = new IdentityUser { UserName = email, Email = email }; var createResult = await _userManager.CreateAsync(user); if (!createResult.Succeeded) return; } // 关联外部登录(若未关联) var externalLoginInfo = await _signInManager.GetExternalLoginInfoAsync(); if (externalLoginInfo != null && !(await _userManager.GetLoginsAsync(user)).Any(l => l.LoginProvider == scheme)) { await _userManager.AddLoginAsync(user, externalLoginInfo); } // 生成服务器自有Bearer token var tokenProvider = HttpContext.RequestServices.GetRequiredService<IBearerTokenProvider>(); var token = await tokenProvider.GenerateAsync(new BearerTokenRequest { Principal = await _signInManager.CreateUserPrincipalAsync(user), Scopes = new[] { "api" } }); // 返回token给移动端 var qs = new Dictionary<string, string> { { "access_token", token.AccessToken }, { "expires", token.ExpiresUtc.ToUnixTimeSeconds().ToString() }, { "email", email } }; var url = callbackScheme + "://#" + string.Join( "&", qs.Where(kvp => !string.IsNullOrEmpty(kvp.Value)) .Select(kvp => $"{WebUtility.UrlEncode(kvp.Key)}={WebUtility.UrlEncode(kvp.Value)}")); Response.Redirect(url); } }
2. 简化API授权配置
统一使用服务器自有Bearer token后,API的授权配置可简化为:
[Route("api/[controller]")] [Authorize(AuthenticationSchemes = IdentityConstants.BearerScheme)] [ApiController] public class MyController() : ControllerBase
3. 验证配置正确性
确保AddBearerToken配置正常,IdentityConstants.BearerScheme是服务器验证自有Bearer token的指定scheme。
额外说明
- Google的access_token仅能用于调用Google官方API,无法直接作为你的服务器API的认证凭证;
- 统一使用服务器自有token后,移动端无需区分登录方式,API调用流程完全一致。
内容的提问来源于stack exchange,提问作者Ryan Langton

