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

ASP.NET Core 5谷歌认证场景下如何获取用户Google角色?

解决方案

一、无需调用API的方案(仅适用于Google Workspace用户)

如果你的用户属于Google Workspace(原G Suite)域,可以通过配置让Google直接在ID Token中返回用户的组/角色信息,无需额外调用API:

  • 登录Google Workspace Admin Console,进入Security > API Controls > Manage Third-Party App Access
  • 找到你的OAuth客户端应用,配置组属性映射,将用户所属组或角色映射到ID Token的自定义Claim中(比如groups或roles)
  • 在ASP.NET Core中,通过ClaimActions.MapJsonKey将该Claim映射到ClaimTypes.Role:
    options.ClaimActions.MapJsonKey(ClaimTypes.Role, "roles", "string");
    

二、调用Google Directory API添加角色Claim(通用方案)

如果是普通Google账号或无法配置Workspace,需要通过调用API获取角色并添加到Claim中,正确的做法是利用OpenIdConnectEvents的OnTokenValidated事件(这是在Token验证通过后、生成ClaimsPrincipal前的扩展点):

修改Startup.cs代码

public class Startup
{
    private IConfiguration Configuration { get; }

    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public void ConfigureServices(IServiceCollection services)
    {
        services
            .AddAuthentication(options =>
            {
                options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            })
            .AddGoogleOpenIdConnect(options =>
            {
                var googleAuthenticationSection = Configuration.GetSection("Authentication:Google");
                options.ClientId = googleAuthenticationSection["ClientId"];
                options.ClientSecret = googleAuthenticationSection["ClientSecret"];
                options.CallbackPath = new("/signin-oidc");
                // 保留所需的Admin Directory权限Scope
                options.Scope.Add("https://www.googleapis.com/auth/admin.directory.group.readonly");
                options.Scope.Add("https://www.googleapis.com/auth/admin.directory.rolemanagement.readonly");
                options.Scope.Add("https://www.googleapis.com/auth/admin.directory.user.readonly");

                options.Events = new OpenIdConnectEvents
                {
                    OnMessageReceived = context =>
                    {
                        if (!string.IsNullOrEmpty(context.ProtocolMessage?.Error))
                        {
                            context.Response.Redirect($"/AccessDenied?error={context.ProtocolMessage.Error}&error_description={context.ProtocolMessage.ErrorDescription}");
                            context.HandleResponse();
                        }
                        return Task.CompletedTask;
                    },
                    // 添加Token验证后的事件处理
                    OnTokenValidated = async context =>
                    {
                        // 获取用户的邮箱(从已验证的Claims中)
                        var userEmail = context.Principal.FindFirstValue(ClaimTypes.Email);
                        if (string.IsNullOrEmpty(userEmail))
                        {
                            return;
                        }

                        // 使用用户的AccessToken创建Google API凭证
                        var accessToken = context.TokenEndpointResponse.AccessToken;
                        var credential = new UserCredential(
                            new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
                            {
                                ClientId = options.ClientId,
                                ClientSecret = options.ClientSecret
                            }), 
                            "user", 
                            accessToken
                        );

                        // 初始化Directory Service
                        var directoryService = new DirectoryService(new BaseClientService.Initializer
                        {
                            HttpClientInitializer = credential,
                            ApplicationName = "Your App Name"
                        });

                        // 调用API获取用户角色(示例:获取用户所属的管理员角色)
                        // 注意:如果是获取组,使用GroupsResource.ListRequest,设置UserKey为用户邮箱
                        var roleAssignmentsRequest = new RoleAssignmentsResource.ListRequest(directoryService, "your-domain.com")
                        {
                            UserKey = userEmail
                        };
                        var roleAssignments = await roleAssignmentsRequest.ExecuteAsync();

                        // 将获取到的角色添加到ClaimsPrincipal中
                        var identity = context.Principal.Identities.First();
                        foreach (var assignment in roleAssignments.Items)
                        {
                            // 根据实际API返回的角色名称调整
                            var roleRequest = new RolesResource.GetRequest(directoryService, "your-domain.com", assignment.RoleId);
                            var role = await roleRequest.ExecuteAsync();
                            identity.AddClaim(new Claim(ClaimTypes.Role, role.RoleName));
                        }
                    }
                };
            })
            .AddCookie();

        services.AddAuthorization(options =>
        {
            options.AddPolicy("WriteAccess", x => x.RequireClaim(ClaimTypes.Role, "Owner"));
        });

        // 注册Google Directory Service(可选,方便后续复用)
        services.AddScoped<DirectoryService>(sp =>
        {
            var config = sp.GetRequiredService<IConfiguration>();
            var googleSection = config.GetSection("Authentication:Google");
            return new DirectoryService(new BaseClientService.Initializer
            {
                ApplicationName = "Your App Name"
            });
        });
    }

    // 补充Configure方法(确保中间件顺序正确)
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler("/Error");
            app.UseHsts();
        }

        app.UseHttpsRedirection();
        app.UseStaticFiles();

        app.UseRouting();

        // 必须按顺序启用Authentication和Authorization中间件
        app.UseAuthentication();
        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllerRoute(
                name: "default",
                pattern: "{controller=Home}/{action=Index}/{id?}");
        });
    }
}

关键说明

  1. 避免在配置阶段执行异步逻辑:你之前在AddGoogleOpenIdConnect的配置委托中直接调用API是错误的,因为这是服务配置阶段,不适合执行运行时的异步操作,应该用OnTokenValidated事件处理。
  2. 使用用户的AccessToken:通过context.TokenEndpointResponse.AccessToken获取用户登录时拿到的AccessToken,以此创建凭证调用API,无需额外的GoogleAuthProvider。
  3. 权限注意事项:确保你的OAuth应用已获得Google Workspace管理员的授权,允许访问Admin Directory相关Scope;普通Google账号无法调用这些API,仅适用于Workspace域用户。

内容的提问来源于stack exchange,提问作者Zvi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 23:54:53