在.NET IdentityServer4中集成Discord OpenIdConnect获取Guilds权限问题
解决IdentityServer中Discord Guilds信息无法获取的问题
核心问题原因
Discord的guilds权限并非通过OIDC标准的id_token直接返回用户的服务器列表,它属于Discord的扩展权限,需要主动调用Discord的API接口才能获取数据。另外你当前配置的自定义IdentityResource中,UserClaims设置为"name"是完全错误的,这和Discord的guilds数据没有关联。
分步解决方案
1. 修正自定义IdentityResource配置
首先调整你的AuthConfig,将guilds对应的IdentityResource的UserClaims改为自定义的Claim类型(比如"discord_guilds"),用于标识我们需要该权限:
public static class AuthConfig { public static IEnumerable<IdentityResource> IdentityResources => new List<IdentityResource> { new IdentityResources.OpenId(), new IdentityResources.Address(), new IdentityResources.Email(), new IdentityResources.Profile(), new IdentityResource() { Name = "guilds", DisplayName = "Discord Guilds", Description = "All of the Discord Guilds the user belongs to", Required = true, Emphasize = true, UserClaims = new[] { "discord_guilds" } // 自定义Claim类型,用于后续存储服务器数据 } }; // ...其他配置 }
2. 配置Discord认证以保存访问令牌
在Startup的Discord认证配置中,开启SaveTokens,确保外部登录后能获取到Discord的访问令牌:
public void ConfigureServices(IServiceCollection services) { services.AddControllersWithViews(); services.AddAuthentication() .AddDiscord("Discord", options => { options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme; options.ClientId = "<my client id>"; options.ClientSecret = "<my client secret>"; options.Scope.Add("guilds"); options.SaveTokens = true; // 关键:保存访问令牌到AuthenticationProperties }); // ...其他IdentityServer配置 }
3. 在外部登录回调中获取Guilds数据
在IdentityServer的外部登录回调逻辑中(通常是UI项目里的ExternalController的Callback方法),获取Discord的访问令牌,调用Discord API获取服务器列表,并将数据添加到用户Claims中:
public async Task<IActionResult> Callback() { // 获取外部登录结果 var result = await HttpContext.AuthenticateAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme); if (!result.Succeeded) { throw new InvalidOperationException("外部登录验证失败"); } // 提取Discord的访问令牌 if (!result.Properties.TryGetTokenValue("access_token", out var accessToken)) { throw new InvalidOperationException("无法获取Discord访问令牌"); } // 调用Discord Guilds API using var httpClient = new HttpClient(); httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); var guildsResponse = await httpClient.GetAsync("https://discord.com/api/users/@me/guilds"); guildsResponse.EnsureSuccessStatusCode(); // 定义模型接收返回数据 var guilds = await guildsResponse.Content.ReadFromJsonAsync<List<DiscordGuild>>(); // 将Guilds数据添加到用户Claims(可根据需求序列化或拆分) var identity = result.Principal.Identity as ClaimsIdentity; if (identity != null) { // 序列化服务器列表为JSON字符串,存入自定义Claim var guildsJson = JsonSerializer.Serialize(guilds); identity.AddClaim(new Claim("discord_guilds", guildsJson)); // 可选:提取特定服务器的权限/角色,转换为标准Role Claim用于授权 foreach (var guild in guilds) { // 示例:如果用户是服务器管理员,添加Admin角色Claim if (guild.Owner || (ulong.Parse(guild.Permissions) & 0x8) != 0) { identity.AddClaim(new Claim(ClaimTypes.Role, "GuildAdmin")); } } } // 清除外部登录Cookie,继续IdentityServer登录流程 await HttpContext.SignOutAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme); // 传递令牌和其他属性到后续登录步骤 var props = new AuthenticationProperties(); props.StoreTokens(result.Properties.GetTokens()); props.Items["scheme"] = "Discord"; return RedirectToPage("/Login/Callback", new { returnUrl = result.Properties.Items["returnUrl"] }); } // 用于序列化Discord返回的Guild数据的模型 public class DiscordGuild { public string Id { get; set; } public string Name { get; set; } public bool Owner { get; set; } public string Permissions { get; set; } }
额外说明
- Discord的
guildsAPI返回的服务器数据包含Id、名称、是否为所有者、权限值等字段,你可以根据业务需求提取需要的信息,比如只保留特定服务器的ID,或者转换为授权用的角色Claim。 - Discord的访问令牌有效期较短(默认15分钟),如果需要长期获取用户的服务器信息,需要在Discord认证配置中请求
offline_accessscope,获取刷新令牌后定期刷新。 - 标准OIDC scope(如
email、profile)能直接拿到Claims,是因为Discord会将这些数据包含在id_token中返回,而guilds属于扩展权限,不在标准OIDC返回范围内,必须主动调用API。
内容的提问来源于stack exchange,提问作者Stephen York
相关产品推荐
相关产品推荐

