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

C# 检查用户是否具有具有特定声明的角色,c#,asp.net,asp.net-mvc,linq,asp.net-identity,C#,Asp.net,Asp.net Mvc,Linq,Asp.net Identity,我想不出做这件事的正确和最有效的方法。我觉得日期和版本很重要,以防将来发生变化(在我寻找答案的过程中,似乎经常发生) 使用Visual Studio 2017v15.5.5,我用个人帐户启动了一个新的MVC ASP.NET Web应用程序(.NET Framework,而不是CORE)。我升级了所有的软件包。目前正在使用EntityFrameworkv6.2.0和Identityv2.2.1 我所做的 [AttributeUsage(AttributeTargets.Method | Attri

我想不出做这件事的正确和最有效的方法。我觉得日期和版本很重要,以防将来发生变化(在我寻找答案的过程中,似乎经常发生)

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

我所做的

[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();
    }
}
我手动实现了自己版本的
RoleClaims
。我有一个定义
RoleClaims
class
,还有一个定义所有应用程序
声明的
class
。在应用程序中,
声明
用于定义可以执行的操作。例如,
声明可以是
查看用户
编辑用户
甚至
删除用户

目标

基于给定的
标识用户
,以及
声明
名称,我想知道用户是否拥有该
声明

课程

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; }
}
摘要

我需要帮助找出一种有效的方法来检查
用户
是否有
声明
,尽管他们的
角色

解决方案


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

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

var roleIds = User.Roles.Select(x => x.Id);
然后获取具有其中一个角色ID的所有声明的名称

var claims = db.RoleClaims.Where(x => roleIds.Contains(x => x.RoleId)).Select(x => x.Claim.Name);
最后测试是否有匹配项

if (claims.Any(x => _claimsSplit.Contains(x)))

但是,由于这将是对每个请求的调用,您应该考虑在<代码>内存MyCase<代码>中缓存结果(假设代码> RoleClaim <代码>不会经常更改)。一种方法是使用

字典
,其中键是
RoleId
,值是
索赔
名称的集合

您可以在启动时使用所有值填充
字典
,也可以根据需要添加到字典中

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;
}
当修改现有的
RoleClaim
或添加新的缓存时,您只需确保缓存无效,以便不使用“过时”数据

注意上面的
缓存
类是

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);
    }
}

什么不适合你的代码?@StephenMuecke试图编写
Linq
表达式,以检查
用户是否通过他们的
角色拥有
声明
,我不确定我是否理解你的关系,但你需要获得一组用户角色,获取所有
RoleClaim
,其中包含
RoleClaim.Claim.Name
匹配的角色。在每个请求中这样做似乎效率很低。@StephenMuecke我仍在学习
Linq
,并仍在研究如何这样做。我也认为这样做效率很低,但你还有什么建议?不确定(仍在试图理解您为什么有
索赔
RoleClaim
课程)谢谢!我想到了两个问题:1)由于我必须为每个请求点击数据库才能获得
RoleIds
,我应该
也缓存这个吗?还是当有太多用户登录并坚持使用数据库访问时效率太低?你有什么建议?2)应用程序将有一组
声明
,只有在重新设计网站时才会更改,可能是50或60,以及4个
角色
。具有的
SysAdmin
角色拥有对所有角色的声明,而其他角色可能拥有2-40。我计划使用
Redis Cache
,我听说每个
CacheItem
有更小的数据位更好。有什么建议吗?我的直觉告诉我倒过来,使用
字典
,其中键是
ClaimName
,值是
RoleIds
第1点的集合-但这就是
haslaim()
方法的目的。它首先检查缓存的字典是否存在,以及它是否包含RoleId的键。如果它这样做,它只从字典中读取值(即,没有数据库调用)。如果它不存在,那么从数据库中获取数据并将其添加到
字典中,以便后续请求不要调用数据库。但是查看我给出的代码,我发现我没有将
roleIds
传递给该方法(稍后我会更新)。第2点-是的,这可能也会起作用。对于第1点,我的意思是缓存
用户的
RoleIds
。除非我不理解部分代码,否则它在哪里缓存
用户的
RoleIds
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);
    }
}