You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 09:52:33