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