ASP.NET Identity中角色与声明的最佳实践

131

我完全不熟悉在ASP.NETIdentity中使用claims,想了解在使用Roles和/或Claims方面的最佳实践。

阅读了这么多后,我仍然有一些问题...

Q: 我们不再使用角色了吗?
Q: 如果是这样的话,为什么还提供角色?
Q: 我们应该只使用声明吗?
Q: 我们应该同时使用角色和声明吗?

我的初步想法是我们"应该"同时使用它们。我将Claims看作支持它们的Roles的子类别。

例如:
Role: 会计
Claims: 可以更新分类帐、只能读取分类帐、可以从分类帐中删除

Q: 它们是否被认为是互斥的?
Q: 还是说只使用声明并且“完全限定”你的声明更好?
Q: 那么这里有哪些最佳实践呢?

例如:同时使用角色和声明
当然,您需要为此编写自己的属性逻辑...

[Authorize(Roles="Accounting")]
[ClaimAuthorize(Permission="CanUpdateLedger")]
public ActionResult CreateAsset(Asset entity)
{
    // Do stuff here

    return View();
}

例子:充分限定你的声明

[ClaimAuthorize(Permission="Accounting.Ledger.CanUpdate")]
public ActionResult CreateAsset(Asset entity)
{
    // Do stuff here

    return View();
}

2
所以,我现在也面临着同样的问题,你是如何解决它并如何在应用程序中进行子角色权限分配的? - Loai
5个回答

91

角色是一种符号类别,用于收集具有相同安全权限级别的用户。基于角色的授权需要首先识别用户,然后确定分配给用户的角色,并最终将这些角色与被授权访问资源的角色进行比较。

相比之下,声明不是基于组的,而是基于身份的。

来自Microsoft文档

创建标识时,可以为其分配一个或多个由受信任方发布的声明。声明是表示主体是什么而不是主体能做什么的名称值对。

稍后的安全检查可以根据一个或多个声明的值确定访问资源的权限。

您可以同时使用两者,也可以在某些情况下使用一种类型,在其他情况下使用另一种类型。这主要取决于与其他系统的互操作性和管理策略。例如,对于管理员来说,管理分配到角色的用户列表可能比管理具有特定声明的用户更容易。在RESTful场景中,声明非常有用,您可以将声明分配给客户端,客户端随后可以呈现声明以进行授权,而不是针对每个请求传递用户名和密码。


14
我认为这并不完全准确。我相信声明表明身份,而不是授权。他们被授权做什么是另外管理的。也就是说,他们可能有一个声明,其中包含他们的出生日期,表明他们已经年满18岁。此声明将传递给授权管理器,该管理器可以包含一条规则,即“如果他们超过18岁,则可以编辑资源X”,但声明本身并不指示他们能够/不能够做什么或访问什么。角色和其他声明也是如此。声明表明你是谁,并用于确定你可以做什么,但它们不直接告诉你。 - ChrisC
1
@ChrisC的支持文档来自于微软的ASP.NET Core中基于声明的授权:"声明是表示主体是什么,而不是主体能做什么的名称值对。" - DrGriff
@DrGriff 感谢您提供链接;我一直在质疑我所描述的准确性;现在我认为根据那个链接我已经澄清了答案。 - Claies

40

正如@Claies所完美解释的那样,声明可以更具描述性,是一种更深层次的角色。我认为它们就像你的角色ID。我有一个健身房ID,所以我属于会员角色。我还参加了踢拳课程,因此我为它们拥有一个踢拳ID声称。我的应用程序需要声明一个新角色以适应我的会员权利。相反,我拥有每个所属小组课程的ID,而不是大量新的会员类型。这就是为什么声明对我更合适。

有一个很好的Barry Dorrans的解释视频,讲述了使用声明而不是角色的优势。他还指出,角色在.NET中仍然存在以实现向后兼容性。该视频非常详细地介绍了声明、角色、策略、授权和身份验证的工作方式。

或者查看Lafi分享的相关会议


5
链接已经失效。视频可以在这里找到:https://www.youtube.com/watch?v=dDroEVdAqKM - Lafi
谢谢你发现了这个问题,@Lafi!我已经更新了链接到新的URL,并且也添加了你的视频链接(它是一个不同的会话)。 - Jonathan Ramos
1
真正有用的内容在第二个视频里 - 我强烈推荐也观看那个。它真的帮助我理解了很多。你可以在这里找到它(目前):https://learn.microsoft.com/en-us/shows/seth-juarez/advanced-aspnet-core-authorization-barry-dorrans - tappetyclick

10

经过几十年的使用各种身份验证和授权技术,我的当前MVC应用程序采用以下方法。

声明(Claims)用于所有授权。用户被分配一个角色(多个角色可能是可行的,但我不需要这些)-更多内容如下。

如常规做法一样,使用了ClaimsAuthorize属性类。由于大多数控制器操作都是CRUD,因此我在代码优先数据库生成中编写了一个例程,该例程迭代所有控制器操作并为每个控制器操作属性创建声明类型,例如Read/Edit/Create/Delete。

[ClaimsAuthorize("SomeController", "Edit")]
[HttpPost]

为在MVC视图中使用,基础控制器类呈现视图包项目

        protected override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            // get user claims
            var user = filterContext.HttpContext.User as System.Security.Claims.ClaimsPrincipal;

            if (user != null)
            {
                // Get all user claims on this controller. In this controler base class, [this] still gets the descendant instance type, hence name
                List<Claim> claims = user.Claims.Where(c => c.Type == this.GetType().Name).ToList();

                // set Viewbag with default authorisations on this controller
                ViewBag.ClaimRead = claims.Any(c => c.Value == "Read");
                ViewBag.ClaimEdit = claims.Any(c => c.Value == "Edit");
                ViewBag.ClaimCreate = claims.Any(c => c.Value == "Create");
                ViewBag.ClaimDelete = claims.Any(c => c.Value == "Delete");
            }

            base.OnActionExecuting(filterContext);
        }

对于网站菜单和其他非控制器操作,我有其他要求。例如,用户是否可以查看特定的货币字段。

bool UserHasSpecificClaim(string claimType, string claimValue)
{
    // get user claims
    var user = this.HttpContext.User as System.Security.Claims.ClaimsPrincipal;

    if (user != null)
    {
        // Get the specific claim if any
        return user.Claims.Any(c => c.Type == claimType && c.Value == claimValue);
    }

    return false;
}

public bool UserHasTradePricesReadClaim
{
    get
    {
        return UserHasSpecificClaim("TradePrices", "Read");
    }
}

角色在哪里适用?

我有一个将角色链接到(默认)声明集的表格。在设置用户授权时,默认情况下是给予用户其角色的声明。每个用户可以比默认值拥有更多或更少的声明。为了使编辑简单,声明列表按控制器和操作(在一行中)显示,其他声明则列出。使用一些Javascript按钮选择一组操作以最小化选择声明所需的“点击”次数。保存时,用户的声明将被删除并添加所有选定的声明。Web应用程序仅加载声明一次,因此任何更改都必须在此静态数据内提示重新加载。

因此,管理者可以选择每个角色中包含哪些声明以及在将用户设置为角色和这些默认声明后用户拥有哪些声明。该系统仅有少量用户,因此管理此数据很简单。


我一直在尝试在 .Net Core 3.1 中使其工作,但是我找不到 ClaimsAuthorize 的参考。它已经不存在了吗?我必须创建自己的 Attribute 吗?谢谢。 - RoLYroLLs
@RoLYroLLs 我认为他可能正在使用自定义授权属性教程:(https://www.c-sharpcorner.com/article/custom-authorization-filter-in-mvc-with-an-example/) 或者使用Thinktecture.IdentityModel.45 (https://github.com/nguyenbanguyen/Thinktecture.IdentityModel.45)。 - iYazee6
1
@iYazee6 感谢提供的参考资料。我已经查看了第一个。然而,这两个参考资料都是针对 .Net Framework 的,而不是 .Net Core,并且在某些名称不属于 .Net Core 并且尚未找到它们的等效项时存在类似的问题。再次感谢。 - RoLYroLLs

5
为了理解角色和声明之间的区别,您必须面对角色的限制,并感受声明如何克服这些问题。让我给您两个场景来认识声明的强大之处,而角色无法解决这些问题:
1- 您的网站有两个模块(页面、服务等)一个是儿童模块(18岁以下),另一个是成人模块(18岁以上)。您的用户身份有一个生日声明。
您需要针对此声明创建一个策略,以便对每个模块授权基于该值进行授权,如果用户年龄超过18岁,则可以进入成人模块而不是在此之前。
角色是布尔数据类型,您可以具有或不具有该角色,它没有多个值。
2- 您的网站有用户角色,您想要防止用户访问某些维护功能,而无需更改代码。
在声明中,您可以创建一个UnderConstrain策略,如果为真,用户不能查看页面,为用户角色授权属性。

2
在撰写本答案时,我们正在使用“.NET 5.0”,而“.NET 6.0”即将推出。以下是我对所见内容的理解:
问:我们不再使用角色了吗?
答:是的,你不应再像之前的框架那样使用角色。
问:如果是这样,为什么还提供角色?
答:为了使项目升级更容易/更快速。
问:我们只应该使用 Claims 吗?
答:是的。但一定要查看由 @Jonathan Ramos 发布在此答案中的视频。
问:我们应该同时使用角色和 Claims 吗?
答:不应该,但当然可以将角色放入声明中,但请确保将项目升级为仅使用 Claims。
你不应该编写自己的属性,而应该使用“策略(policy)”来进行操作,因为这是较新框架的方法。如果你需要自己的属性,那么你就“做错了”,只需创建自己的 Requirement(handler),这就是整个“新”策略的意义所在。 在当前框架中,甚至不再提供 ClaimAuthorize 属性。

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