我无法弄清楚这样做的正确和最有效的方法。我觉得日期和版本对于将来发生变化很重要(似乎经常发生,因为我正在寻找答案)。
使用Visual Studio 2017
v15.5.5,我使用个人帐户启动了一个新的MVC ASP.NET Web应用程序(.Net Framework,而不是CORE)。我升级了所有软件包。当前使用EntityFramework
v6.2.0 和Identity
v2.2.1。
我做了什么
我手动实现了我自己的RoleClaims
版本。我有一个定义RoleClaims
的class
和另一个定义所有应用程序Claims
class
。在应用程序中,Claims
用于定义可以执行的操作。例如,Claim
可以是View Users
的,也可以是Edit Users
的,甚至是Delete Users
的。
目标
根据给定的IdentityUser
和Claim
名称,我想知道用户是否有该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; }
}
更多背景
我更喜欢在Linq
表达式中给出答案,但Linq
查询也可以。
此外,我正在自定义AuthorizeAttribute
中执行所有这些逻辑,基于以下内容,因此我很有可能做错了其他事情。 =)随意评论它。
- 编写自己的自定义 ASP.Net MVC [授权] 属性
- System.Web.Http.AuthorizeAttribute.cs
声明授权属性
[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();
}
}
总结
我需要帮助找出一种有效的方法来检查User
是否通过他们的Roles
Claim
。
溶液
对于任何感兴趣的人,感谢Stephen Muecke,我能够创建解决方案。任何有兴趣的人都可以查看我的 ClaimsAuthorizationAttribute.cs 版本。
假设你有User
(例如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)))
但是,由于这将在每个请求上调用,因此应考虑将结果缓存在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;
}
并称其为AuthorizeCore()
return HasClaim(_claimsSplit);
您只需要确保在修改现有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);
}
}