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

在.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的guilds API返回的服务器数据包含Id、名称、是否为所有者、权限值等字段,你可以根据业务需求提取需要的信息,比如只保留特定服务器的ID,或者转换为授权用的角色Claim。
  • Discord的访问令牌有效期较短(默认15分钟),如果需要长期获取用户的服务器信息,需要在Discord认证配置中请求offline_access scope,获取刷新令牌后定期刷新。
  • 标准OIDC scope(如email、profile)能直接拿到Claims,是因为Discord会将这些数据包含在id_token中返回,而guilds属于扩展权限,不在标准OIDC返回范围内,必须主动调用API。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 13:30:51