通过 AddIdentityServer 向 IdentityServer 设置添加声明

Posted

技术标签:

【中文标题】通过 AddIdentityServer 向 IdentityServer 设置添加声明【英文标题】:Adding claims to IdentityServer setup by AddIdentityServer 【发布时间】:2019-10-10 10:46:58 【问题描述】:

我有一个 SPA,它有一个 ASP.NET Core Web API 以及使用 AddIdentityServerAddIdentityServerJwt 打开的内置身份服务器:

services.AddIdentityServer()
   .AddApiAuthorization<User, UserDataContext>();
services.AddAuthentication()
   .AddIdentityServerJwt();

我还有一个需要“管理员”角色声明的授权策略设置:

services.AddAuthorization(options =>

    options.AddPolicy("IsAdmin", policy => policy.RequireClaim(ClaimTypes.Role, "Admin")); 
);

我有一个使用此策略的控制器操作

[Authorize(Policy = "IsAdmin")]
[HttpDelete("id")]
public IActionResult Deleten(int id)

    ...

经过身份验证的用户确实拥有“管理员”角色声明:

此身份验证用户的访问令牌似乎不包含管理员声明:

我在尝试向管理员用户请求此资源时返回 403:

所以,如果我理解正确的话,IdentityServer 不包括管理员角色声明,因此用户无权访问该资源。

是否可以使用 AddIdentityServerJwt 配置 IdentityServer 使用的声明?还是我误解了为什么这不起作用。

【问题讨论】:

您需要将声明添加到您的令牌中。我会在早上给你发一些代码 我相信这是因为实现不会自动映射自定义声明。请参阅我关于到目前为止发现的问题:***.com/questions/56368522/… 【参考方案1】:

在 Identity Server 端,您可以创建 Profile Service 以使 IDS4 在颁发令牌时包含 role 声明。

您可以从 ClaimsPrincipal 获取角色声明或从数据库获取角色并创建配置文件服务,例如:

public class MyProfileService : IProfileService

    public MyProfileService()
     

    public Task GetProfileDataAsync(ProfileDataRequestContext context)
    
        //get role claims from ClaimsPrincipal 
        var roleClaims = context.Subject.FindAll(JwtClaimTypes.Role);

        //add your role claims 
        context.IssuedClaims.AddRange(roleClaims);
        return Task.CompletedTask;
    

    public Task IsActiveAsync(IsActiveContext context)
    
        // await base.IsActiveAsync(context);
        return Task.CompletedTask;
    

并在 Startup.cs 中注册:

services.AddTransient<IProfileService, MyProfileService>();

在客户端,您应该从您的 JWT 令牌映射角色声明,并在 AddOpenIdConnect 中间件中尝试以下配置:

  options.ClaimActions.MapJsonKey("role", "role", "role");
  options.TokenValidationParameters.RoleClaimType = "role";

然后您的 api 可以验证访问令牌并使用角色策略进行授权。

【讨论】:

不幸的是,我没有使用 AddOpenIdConnect 中间件,因为这是一个 SPA。我通过使用 AddIdentityServerJwt 将 Web API 与身份服务器结合在一起。我正在努力弄清楚如何使用 AddIdentityServerJwt 配置声明,因为它似乎没有任何配置选项 @CarlRippon 我认为不需要配置 OpenIdConnect。如果你想看看,我已经answered my own post。【参考方案2】:

其他答案之一非常接近所讨论的特定用例,但忽略了它是 SPA 的重点。

首先,您必须像已经建议的那样添加您的 IProfileService 实现:

public class MyProfileService : IProfileService

    public MyProfileService()
     

    public Task GetProfileDataAsync(ProfileDataRequestContext context)
    
        //get role claims from ClaimsPrincipal 
        var roleClaims = context.Subject.FindAll(JwtClaimTypes.Role);

        //add your role claims 
        context.IssuedClaims.AddRange(roleClaims);
        return Task.CompletedTask;
    

    public Task IsActiveAsync(IsActiveContext context)
    
        // await base.IsActiveAsync(context);
        return Task.CompletedTask;
    

然后继续这样做:

services.AddIdentityServer()
                .AddApiAuthorization<ApplicationUser, ApplicationDbContext>()
                .AddProfileService<MyProfileService>();

您的声明将在 JWT 上公开。将 ClaimTypes.Role 常量替换为与您要公开的声明类型相对应的任何字符串。

【讨论】:

哇,我一直在寻找这个答案!谢谢!【参考方案3】:

我没有使用角色,而是使用添加到用户令牌的特殊声明来执行此操作。我创建了一个 CustomUserClaimsPrincipalFactory,它允许我向用户添加额外的声明。

注册

services.AddScoped<IUserClaimsPrincipalFactory<ApplicationUser>, CustomUserClaimsPrincipalFactory>();

代码。

public class CustomUserClaimsPrincipalFactory : UserClaimsPrincipalFactory<ApplicationUser, IdentityRole<long>>
    
        public CustomUserClaimsPrincipalFactory(
            UserManager<ApplicationUser> userManager,
            RoleManager<IdentityRole<long>> roleManager,
            IOptions<IdentityOptions> optionsAccessor)
            : base(userManager, roleManager, optionsAccessor)
        
        

        protected override async Task<ClaimsIdentity> GenerateClaimsAsync(ApplicationUser user)
          
            var userId = await UserManager.GetUserIdAsync(user);
            var userName = await UserManager.GetUserNameAsync(user);
            var id = new ClaimsIdentity("Identity.Application", 
                Options.ClaimsIdentity.UserNameClaimType,
                Options.ClaimsIdentity.RoleClaimType);
            id.AddClaim(new Claim(Options.ClaimsIdentity.UserIdClaimType, userId));
            id.AddClaim(new Claim(Options.ClaimsIdentity.UserNameClaimType, user.Name));
            id.AddClaim(new Claim("preferred_username", userName));
            id.AddClaim(new Claim("culture", user.Culture ?? "da-DK"));
            if (UserManager.SupportsUserSecurityStamp)
            
                id.AddClaim(new Claim(Options.ClaimsIdentity.SecurityStampClaimType,
                    await UserManager.GetSecurityStampAsync(user)));
            
            if (UserManager.SupportsUserClaim)
            
                id.AddClaims(await UserManager.GetClaimsAsync(user));
            

            if(user.IsXenaSupporter)
               id.AddClaim(new Claim("supporter", user.Id.ToString()));
            return id;
        
    

政策

services.AddAuthorization(options =>
            
                options.AddPolicy("Supporter", policy => policy.RequireClaim("supporter"));
            );

用法

    [Authorize(AuthenticationSchemes = "Bearer", Policy = "Supporter")]
    [HttpPost("supporter")]
    public async Task<ActionResult> ChangeToSpecificUser([FromBody] ChangeUserRequest request)
    

     // .................. 

    

【讨论】:

以上是关于通过 AddIdentityServer 向 IdentityServer 设置添加声明的主要内容,如果未能解决你的问题,请参考以下文章

通过 socket.id 向客户端发送消息

通过 discord.py 中的用户 ID 向某人发送消息

Magento:通过 id 向特定用户添加送货地址

Firebase:通过设备 ID 向 JavaScript 客户端(react-native)发送消息

通过JavaScript按ID向对象注入HTML

通过 ID 与公式组合(或类似 tidy tibble 上的时间序列操作)向 tibble /dataframes 添加额外的行