检查用户是否具有具有特定声明的角色

RoL*_*LLs 3 c# linq asp.net asp.net-mvc asp.net-identity

我无法找出正确且最有效的方法来做到这一点。我觉得日期和版本很重要,以防将来事情发生变化(在我寻找答案时似乎经常发生)。

使用Visual Studio 2017v15.5.5,我使用个人帐户启动了一个新的 MVC ASP.NET Web 应用程序(.Net Framework,而不是 CORE)。我升级了所有的包。目前使用EntityFrameworkv6.2.0 和Identityv2.2.1。

我做了什么

我手动实现了我自己的版本RoleClaims。我有一个class定义了RoleClaims应用class程序的所有Claims. 在应用程序中,Claims用于定义可以做什么。例如,aClaim可以是View UsersorEdit Users甚至Delete Users

目标

根据给定的IdentityUserClaim名称,我想知道用户是否有该名称Claim

课程

public class RoleClaim {
    public int Id { get; set; }
    public string RoleId { get; set; }
    public IdentityRole Role { get; set; }
    public int ClaimId { get; set; }
    public Claim Claim { get; set; }
}

public class Claim {
    public int Id { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public string ClaimType { get; set; }
    public virtual ICollection<RoleClaim> RoleClaims { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

更多背景信息

我更喜欢用表达式来回答Linq,但Linq查询也可以。

另外,我AuthorizeAttribute根据以下内容在自定义中执行所有这些逻辑,因此我很可能会做其他错误的事情。=) 请随意发表评论。

声明授权属性

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = true)]
public class ClaimsAuthorizationAttribute : AuthorizeAttribute {
    private static readonly string[] _emptyArray = new string[0];
    private string _claims;
    private string[] _claimsSplit = _emptyArray;

    public string Claims {
        get => _claims ?? string.Empty;
        set {
            _claims = value;
            _claimsSplit = SplitString(value);
        }
    }

    public override void OnAuthorization(AuthorizationContext filterContext) {
        if (filterContext == null) {
            throw new ArgumentException("filterContext");
        }

        if (AuthorizeCore(filterContext.HttpContext)) {
            // allowed... research anything else needed to be done
        } else {
            HandleUnauthorizedRequest(filterContext);
        }
    }

    protected override bool AuthorizeCore(HttpContextBase httpContext) {
        if (httpContext == null) {
            throw new ArgumentException("httpContext");
        }

        var user = httpContext.User;
        if (!user.Identity.IsAuthenticated) {
            return false;
        }

        /*
         * _claimsSplit contains all allowed Claims with access
         * 
         * Based on the list of Claims, check if any of the Roles 
         * the user is a member of has at least the same Claim
         * 
         * OR
         * 
         * Based on the users' Roles, check if any of those 
         * roles has at least one of the claims that were passed in
         * 
         * 
         * 
         * Should I check for any or should it be ALL Claims 
         * passed in? or should I pass another variable (bool) 
         * allowing the ability to decide if it should be 
         * at least 1 or all?
        */

        return true;
    }

    internal static string[] SplitString(string original) {
        if (string.IsNullOrEmpty(original)) {
            return _emptyArray;
        }

        var split = from piece in original.Split(',')
                    let trimmed = piece.Trim()
                    where !string.IsNullOrEmpty(trimmed)
                    select trimmed;
        return split.ToArray();
    }
}
Run Code Online (Sandbox Code Playgroud)

概括

我需要帮助找出一种有效的方法来检查 a 是否User拥有Claim他们的Roles.

解决方案

对于任何感兴趣的人,感谢Stephen Muecke,我能够创建解决方案。任何感兴趣的人都可以查看我的ClaimsAuthorizationAttribute.cs版本。

小智 5

假设您有User(例如var User = db.Users.FirstOrDefault(u => u.UserName == user.Identity.Name),您可以首先获取用户的角色 ID 的集合

var roleIds = User.Roles.Select(x => x.Id);
Run Code Online (Sandbox Code Playgroud)

然后获取具有这些角色 ID 之一的所有声明的名称

var claims = db.RoleClaims.Where(x => roleIds.Contains(x => x.RoleId)).Select(x => x.Claim.Name);
Run Code Online (Sandbox Code Playgroud)

最后测试是否有匹配项

if (claims.Any(x => _claimsSplit.Contains(x)))
Run Code Online (Sandbox Code Playgroud)

但是,由于这将在每个请求上调用,因此您应该考虑缓存结果MemoryCache(假设RoleClaim不会经常更改)。一种方法是使用一个,Dictionary<string, IEnumerable<string>>其中键是RoleId,值是Claim名称的集合。

您可以Dictionary在启动时填充所有值,也可以根据需要添加。伪代码类似于

private const string key = "RoleClaims"

private bool HasClaim(string[] requiredClaims)
{
    // Check the cache
    Dictionary<string, IEnumerable<string>> roleClaims = Cache.Get(key)
    if (roleClaims == null)
    {
        roleClaims = new Dictionary<string, IEnumerable<string>>();
        Cache.Set(key, roleClaims, 240);
    }
    foreach (var role in roleIds)
    {
        IEnumerable<string> claims;
        if (roleClaims.ContainsKey(role))
        {
            claims = roleClaims[role];
        }
        else
        {
            claims = db.RoleClaims.Where(roleIds == role).Select(x => x.Claim.Name);
            roleClaims.Add(role, claims)
        }
        if (claims.Any(x => requiredClaims.Contains(x)))
        {
            return true // exit
        }
    }
return false;
}
Run Code Online (Sandbox Code Playgroud)

并将其称为AuthorizeCore()使用

return HasClaim(_claimsSplit);
Run Code Online (Sandbox Code Playgroud)

您只需要确保缓存在RoleClaim修改现有缓存或添加新缓存时失效,这样您就不会使用“过时”数据。

注意Cache上面的类是

public static class Cache
{
    public static object Get(string key)
    {
        return MemoryCache.Default[key];
    }
    public static void Set(string key, object data, int duration = 30)
    {
        CacheItemPolicy policy = new CacheItemPolicy();
        policy.AbsoluteExpiration = DateTime.Now + TimeSpan.FromMinutes(duration);
        MemoryCache.Default.Add(new CacheItem(key, data), policy);
    }
    public static void Invalidate(string key)
    {
        MemoryCache.Default.Remove(key);
    }
}
Run Code Online (Sandbox Code Playgroud)