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?}"); }); } }
关键说明
- 避免在配置阶段执行异步逻辑:你之前在
AddGoogleOpenIdConnect的配置委托中直接调用API是错误的,因为这是服务配置阶段,不适合执行运行时的异步操作,应该用OnTokenValidated事件处理。 - 使用用户的AccessToken:通过
context.TokenEndpointResponse.AccessToken获取用户登录时拿到的AccessToken,以此创建凭证调用API,无需额外的GoogleAuthProvider。 - 权限注意事项:确保你的OAuth应用已获得Google Workspace管理员的授权,允许访问
Admin Directory相关Scope;普通Google账号无法调用这些API,仅适用于Workspace域用户。
内容的提问来源于stack exchange,提问作者Zvi
相关产品推荐
相关产品推荐

