如何在IdentityServer4运行时添加/管理用户声明

6
我正在尝试在一个新项目中使用IdentityServer4。在PluralSight视频“理解ASP.NET Core安全性”中,我看到了可以使用基于声明的安全性来保护Web API的IdentityServer4。我已将我的IdentityServer4设置为单独的项目/解决方案。
我还看到您可以添加IProfileService以向由IdentityServer4返回的令牌添加自定义声明。
其中一个计划是向用户添加新声明,以授予他们访问API不同部分的权限。但是,我无法弄清楚如何从api项目管理IdentityServer用户的声明。我假设我应该调用IdentotyServer4来添加和删除用户的声明?
此外,总的来说,这是否是一个好方法?因为我不确定允许客户端为其自身的内部安全目的添加声明是否有意义-并且可能会导致冲突(例如,多个客户端使用值为“admin”的'role'声明)。也许我应该在api项目中本地处理安全性,然后只使用'sub'声明来查找它们?
请问是否有任何好的解决方法?

2
声明涉及身份验证而非权限。https://leastprivilege.com/2016/12/16/identity-vs-permissions/ - leastprivilege
1
嗨@leastprivilege,感谢提供链接。该链接确实表明了您的观点,但我仍然有些困惑,因为在这个视频中(您被认为是帮助创建的课程的一部分),声明被用于授权。该视频名为“基于声明的策略授权”。您是否不同意这种方法?您是否有任何关于正确结合IdentityServer4和授权的信息链接?再次感谢 https://app.pluralsight.com/player?course=asp-dot-net-core-security-understanding&author=roland-guijt&name=c80587ee-9819-4b66-b743-6273ca1f32ff&clip=1&mode=live - Levitybot
我还没有观看PluralSight课程。我在我的博客文章中已经说了所有的话。一个令牌不是权限的好传输机制。如果你决定使用字符串集合(也称为ClaimsPrincipal)来保存你的内存授权数据 - 那就由你决定。 - leastprivilege
1个回答

5

这是一个旧问题,但仍然相关。leastprivilege在评论中说:

声明涉及身份验证 - 而不是权限。

这是正确的,但身份还可以包括用户类型(管理员、用户、经理等),这可以用于在API中确定权限。也许设置具有特定权限的用户角色?实际上,您还可以将角色按客户端分开以获得更多控制,例如如果CLIENT1-Admin不应具有与CLIENT2-Admin相同的权限,则可以进行划分。

因此,在您的IProfileService中传递您的角色作为声明。

public class ProfileService : IProfileService
{
    private readonly Services.IUserService _userService;

    public ProfileService(Services.IUserService userService)
    {
        _userService = userService;
    }

    public async Task GetProfileDataAsync(ProfileDataRequestContext context)
    {
        try
        {
            switch (context.Client.ClientId)
            {
                //setup profile data for each different client
                case "CLIENT1":
                {
                    //sub is your userId.
                    var userId = context.Subject.Claims.FirstOrDefault(x => x.Type == "sub");

                    if (!string.IsNullOrEmpty(userId?.Value) && long.Parse(userId.Value) > 0)
                    {
                        //get the actual user object from the database
                        var user = await _userService.GetUserAsync(long.Parse(userId.Value));

                        // issue the claims for the user
                        if (user != null)
                        {
                            var claims = GetCLIENT1Claims(user);

                            //add the claims
                            context.IssuedClaims = claims.Where(x => context.RequestedClaimTypes.Contains(x.Type)).ToList();
                        }
                    }
                }
                break;
                case "CLIENT2":
                {
                    //...
                }
            }
        }
        catch (Exception ex) 
        {
            //log your exceptions
        }
    }

    // Gets all significant user claims that should be included
    private static Claim[] GetCLIENT1Claims(User user)
    {
        var claims = new List<Claim>
        {
            new Claim("user_id", user.UserId.ToString() ?? ""),
            new Claim(JwtClaimTypes.Name, user.Name),
            new Claim(JwtClaimTypes.Email, user.Email ?? ""),
            new Claim("some_other_claim", user.Some_Other_Info ?? "")
        };

        //----- THIS IS WHERE ROLES ARE ADDED ------
        //user roles which are just string[] = { "CLIENT1-Admin", "CLIENT1-User", .. }
        foreach (string role in user.Roles)
            claims.Add(new Claim(JwtClaimTypes.Role, role));

        return claims.ToArray();
    }
}

然后,给你的控制器添加 [Authorize] 属性以配置特定权限。这只允许特定角色访问它们,从而设置自己的权限。

[Authorize(Roles = "CLIENT1-Admin, CLIENT2-Admin, ...")]
public class ValuesController : Controller
{
    //...
}

如果您正在使用自定义的ResourceOwnerPasswordValidator来设置ResourceOwner,则上述声明也可以在身份验证中传递。您可以像以下方式一样在Validation方法中传递声明。请注意保留HTML标记。ResourceOwnerPasswordValidator是指验证ResourceOwner密码的类。
context.Result = new GrantValidationResult(
    subject: user.UserId.ToString(),
    authenticationMethod: "custom",
    claims: GetClaims(user));

所以像leastprivilege所说的那样,您不希望使用IdentityServer来设置权限并将其作为声明传递(例如谁可以编辑哪个记录),因为它们太具体化并且会使令牌混乱,但是设置角色以授予它们访问不同部分的API是完全可以的。这对于用户角色来说非常合适。希望能有所帮助。

1
由于答案非常近期,您有任何关于如何在运行时添加用户的想法吗? - Wouter Vanherck
1
@Wouter Vanherck 不确定您的用例,但您是否意味着将新用户注册到系统中?注册与未使用Identity Server的普通asp.net web应用程序完全相同(Identity Server更多用于验证已经存在的用户)。只有登录会发出令牌并进行验证,您应该调用Identity Server。请查看Visual Studio中默认的ASP.NET模板中AccountsController中的默认Register函数。如果这不能回答您的问题,请在Stack Overflow上创建一个新的问题,并在此处链接它,我将很乐意回答。 - Nick De Beer
嘿@Nick De Beer,感谢您的回复。我实际上只是想使用身份验证服务器提供的令牌。我的用户在第三方API中,无法通过常规方法注册。我仍然会检查您关于AccountsController的建议,因为它可能会对我有所帮助。在我提问之前还需要进行更多研究,但我会在那时标记您。提前致谢! - Wouter Vanherck
1
@Wouter Vanherck 没问题。您将不得不解释通过正常方法无法注册用户是什么意思。 - Nick De Beer
1
@Wouter Vanherck 通常外部API的工作方式相同,您将身份服务器设置为资源所有者类型。 您不会使用身份服务器注册用户,只能用于验证现有用户。 因此,您正常地(客户端)使用您的API,调用您的注册(匿名调用),将新用户插入您的数据库中。 然后,当用户想要登录时,您调用您的身份服务器端点/connect/token。 当您设置身份服务器时,您需要设置验证,以调用API(安全-CORS和ssl首选项)来检查用户名和密码是否存在。 - Nick De Beer
1
@Wouter Vanherck,请查看我的个人资料,我对资源所有者设置(IdentityServer 4注册UserService并使用asp.net core从数据库获取用户)有一个非常详细的答案。您只需要更改userRepository.FindAync()调用以调用您的API,在第三方API中查找用户并将其返回给IdentityServer即可。希望能帮到您。 - Nick De Beer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接