使用Identity Claims控制MVC菜单,控制器授权和视图

时间:2018-01-30 16:22:06

标签: asp.net-mvc authorization claims

我一直在寻找使用最新身份声明功能在MVC中实现授权的方法(多年来使用Active Directory实现一切!)。我在下面发布了一条建议,但我的问题是,有没有人有更好的想法?还有更多"标准"实现这一目标的方法?

在当天,Forms Authentication Users和Roles是控制Web应用程序中的身份验证和授权的方式。通常使用与业务角色相对应的少量角色(例如,管理员,经理,工人)。有可能(现在仍然)以更高的粒度级别使用角色,比如使用字符串为每个MVC [控制器] + [动作]创建一个角色。这确实需要额外的表来以较低的粒度级别管理业务角色,但它可以非常简单地完成。

随着"声明"的出现在最新的ASP.NET身份中,我认为可以实现更好的授权实现。有很多方法可以做到这一点,我建议在下面给出一个相当小的方法。

1 个答案:

答案 0 :(得分:0)

创建一个Controller基类并覆盖OnActionExecuting方法。我为数据库中的用户保存了一些声明,声明控制器名称的声明类型和声明值为"阅读","编辑","创建" ,"删除",这是用户在控制器上的典型声明。我已将这些声明添加到视图包中,以便在视图中使用。

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

控制器操作使用自定义授权来保护它们

[ClaimsAuthorize("ApplianceTypesController", "Create")]
public ActionResult Create()
{
    return View();
}

在Razor视图中,仅在授权

时添加控件
<p>
    @if (ViewBag.ClaimCreate)
    {
        @Html.ActionLink("Create New", "Create")
    }
</p>

我没有将额外的授权放入控制器和其他地方,而是将其放入自定义授权类

public class ClaimsAuthorizeAttribute : AuthorizeAttribute
{
    private string claimType;
    private string claimValue;

    public ClaimsAuthorizeAttribute(string type, string value = "")
    {
        this.ClaimType = type;
        this.ClaimValue = value;
    }

    public string ClaimType { get => claimType; protected set => claimType = value; }

    public string ClaimValue { get => claimValue; protected set => claimValue = value; }

    public override void OnAuthorization(AuthorizationContext filterContext)
    {
        if (filterContext.HttpContext.User != null)
        {
            var user = filterContext.HttpContext.User as System.Security.Claims.ClaimsPrincipal;

            if (user != null && user.HasClaim(ClaimType, ClaimValue))
            {
                filterContext.Result = null;
                base.OnAuthorization(filterContext);
            }
            else
            {
                // we don't use 401 as this will cause a login loop :  base.HandleUnauthorizedRequest(filterContext);
                filterContext.Result = new HttpStatusCodeResult(HttpStatusCode.Forbidden, "You are forbidden to access this resource");
            }
        }
    }
}

我没有使用标准的Site-Map,而是有一个基于Claim类型的菜单项表(忽略任何值)。我假设对控制器及其页面的任何声明都会显示菜单项以实现目标。这作为菜单

的一部分存在于局部视图中
@foreach (var item in Model.Where(m => m.ParentId == null).OrderBy(m => m.SequenceNumber))
{
    @Html.ActionLink(item.DisplayText, item.ActionName, item.ControllerName, null, new { @class = "nav-item nav-link active" })
}