在我的 ASP.NET MVC 站点中,我应该将访问权限的代码放在哪里? [英] Where should I put code for access permisions in my ASP.NET MVC Site?

查看:26
本文介绍了在我的 ASP.NET MVC 站点中,我应该将访问权限的代码放在哪里?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个 ASP.NET MVC 站点,它利用存储库模式来访问和修改数据.我的存储库接口通过它们的构造函数传递给每个控制器.我还使用 Ninject 通过 DependencyResolver.SetResolver() 注入我的具体存储库类型.

I have an ASP.NET MVC site which makes use of the repository pattern for accessing and modifying data. My repository interface is passed to each controller through their constructors. I'm also using Ninject to inject my concrete repository type through DependencyResolver.SetResolver().

我网站上的用户应该只能访问分配给他们的数据.我想弄清楚的是我应该在哪里检查当前用户是否有权执行当前请求?

Users on my site should only be able to access data that is assigned to them. What I'm trying to figure out is where should I check that the current user has permission to perform the current request?

例如,用户可能会向 URL/Item/Delete/123 提交删除项目确认表单,这将删除 ID 为 123 的项目.但是,我不希望用户能够操纵此 URL 并最终删除另一个用户的项目.

For example the user may submit an delete item confirmation form to the URL /Item/Delete/123, which will delete the Item with ID 123. However, I don't want a user to be able to manipulate this URL and end up deleting another user's Item.

我是否应该将用户验证代码添加到控制器,以便每个操作方法做的第一件事就是检查当前用户是否拥有他们试图修改的数据?这似乎会给应该相当薄的控制器增加太多智能.

Should I add the user validation code to the controllers so the first thing each action method does is check if the current user even owns the data they are trying to modify? This seems like it would be adding too much intelligence to the controller which should be rather thin.

我认为将此逻辑添加到我的存储库会更有意义吗?例如,我可能有一个 Repository.GetItem(int id, string user) 而不是 Repository.GetItem(int id),除非用户"拥有请求的项目,否则它会抛出异常.

I think it would make more sense to add this logic to my repository? For example I may have a Repository.GetItem(int id, string user) rather then Repository.GetItem(int id), which would throw an exception unless "user" owns the requested item.

或者,我认为我创建的存储库的每个实例在实例化时都可以分配给特定用户.如果曾经尝试访问或修改不属于当前用户的数据,这些用户特定的存储库将抛出异常.然后控制器只需要捕获这些异常并在捕获到错误时将用户重定向到错误页面.

Alternatively I was thinking that each instance of my repository created could be assigned to a specific user when it is instantiated. These user specific repository would then throw exceptions if there is ever an attempt to access or modify data that is not owned by the current user. The controller would then simply need to catch theses exceptions and redirect the user to an error page if one is caught.

推荐答案

我最近遇到了完全相同的问题.我最终使用了一个继承自 AuthorizeAttribute 的自定义 ActionFilter.

I recently came across the exact same issue. I ended up going with a custom ActionFilter that inherits from AuthorizeAttribute.

它基本上具有与 Authorize 相同的功能(检查用户是否至少属于列出的角色之一),但还增加了检查用户是否拥有"特定数据的功能.

It basically has the same functionality as Authorize (checks if a user belongs to at least one of the roles listed) but also adds the capability to check if a user "owns" the particular data.

以下是供您用作示例的代码.如果有什么不清楚的,请评论,我会尽力解释.

Here's the code for you to use as an example. If anything is not clear, please comment, and I'll try to explain.

[Edit - 根据 Ryan 的建议,我将 params UserRole[] 设为构造函数参数而不是公共属性,并添加了 AllowAnyRolesIfNoneSpecified.]

[Edit - Based on Ryan's suggestion, I made params UserRole[] a constructor parameter instead of a public property and added AllowAnyRolesIfNoneSpecified.]

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public class AccountAuthorizeAttribute : AuthorizeAttribute
{
    private readonly UserRole[] _userRoles;

    public bool MustBeInRoleOrPageOwner { get; set; }
    public bool MustBeInRoleAndPageOwner { get; set; }
    public bool MustBeInRoleAndNotPageOwner { get; set; }
    public bool AllowAnyRolesIfNoneSpecified { get; set; }
    public string AccessDeniedViewName { get; set; }

    public AccountAuthorizeAttribute(params UserRole[] userRoles)
    {
        _userRoles = userRoles;
        MustBeInRoleOrPageOwner = false;
        MustBeInRoleAndPageOwner = false;
        MustBeInRoleAndNotPageOwner = false;
        AllowAnyRolesIfNoneSpecified = true;
        AccessDeniedViewName = "AccessDenied";
    }

    protected void CacheValidateHandler(HttpContext context, object data, ref HttpValidationStatus validationStatus)
    {
        validationStatus = OnCacheAuthorization(new HttpContextWrapper(context));
    }

    public override void OnAuthorization(AuthorizationContext filterContext)
    {
        if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
        {
            ShowLogOnPage(filterContext);
            return;
        }
        using (var dbContext = new MainDbContext())
        {
            var accountService = new AccountService(dbContext);
            var emailAddress = filterContext.HttpContext.User.Identity.Name;
            if (IsUserInRole(accountService, emailAddress))
            {
                var isPageOwner = IsUserPageOwner(filterContext, dbContext, accountService, emailAddress);
                if (MustBeInRoleAndPageOwner && !isPageOwner || MustBeInRoleAndNotPageOwner && isPageOwner)
                    ShowAccessDeniedPage(filterContext);
            }
            else
            {
                if (!MustBeInRoleOrPageOwner)
                    ShowAccessDeniedPage(filterContext);
                else if (!IsUserPageOwner(filterContext, dbContext, accountService, emailAddress))
                    ShowAccessDeniedPage(filterContext);
            }
        }
    }

    private bool IsUserInRole(AccountService accountService, string emailAddress)
    {
        if (_userRoles.Length == 0 && AllowAnyRolesIfNoneSpecified) return true;
        return accountService.IsUserInRole(emailAddress, _userRoles);
    }

    protected virtual bool IsUserPageOwner(
        AuthorizationContext filterContext, MainDbContext dbContext, AccountService accountService, string emailAddress)
    {
        var id = GetRouteId(filterContext);
        return IsUserPageOwner(dbContext, accountService, emailAddress, id);
    }

    protected int GetRouteId(AuthorizationContext filterContext)
    {
        return Convert.ToInt32(filterContext.RouteData.Values["id"]);
    }

    private bool IsUserPageOwner(MainDbContext dbContext, AccountService accountService, string emailAddress, int id)
    {
        return accountService.IsUserPageOwner(emailAddress, id);
    }

    private void ShowLogOnPage(AuthorizationContext filterContext)
    {
        filterContext.Result = new HttpUnauthorizedResult();
    }

    private void ShowAccessDeniedPage(AuthorizationContext filterContext)
    {
        filterContext.Result = new ViewResult { ViewName = "ErrorPages/" + AccessDeniedViewName };
    }

    private void PreventPageFromBeingCached(AuthorizationContext filterContext)
    {
        var cachePolicy = filterContext.HttpContext.Response.Cache;
        cachePolicy.SetProxyMaxAge(new TimeSpan(0));
        cachePolicy.AddValidationCallback(CacheValidateHandler, null);
    }
}

一些注意事项:

为了避免魔法字符串",我使用了一组 UserRole 枚举值而不是单个字符串.此外,我构建了它来处理我遇到的几种情况:

To avoid "magic strings", I used an array of UserRole enum values instead of a single string. Also, I built it to handle several scenarios I came across:

  • 用户必须是一个角色页面/数据的所有者(例如,管理员可以编辑任何人的数据,任何人都可以编辑自己的数据)
  • 用户必须是一个角色页面/数据的所有者(例如,用户只能编辑他/她自己的页面/数据——通常使用没有任何角色限制)莉>
  • 用户必须是一个角色并且不是页面/数据的所有者(例如,管理员可以编辑除他自己之外的任何人的页面/数据——例如,防止管理员删除他的自己的帐户)
  • 不允许用户查看此页面,AllowAnyRolesIfNoneSpecified = false(例如,您有一个不存在的页面的控制器方法,但您需要包含该方法,因为您的控制器继承自具有此方法的基类)
  • User must be in a role or the owner of the page/data (e.g., an admin can edit anyone's data and anyone can edit their own data)
  • User must be in a role and the owner of the page/data (e.g., a user can only edit his/her own page/data -- usually used without any role restrictions)
  • User must be in a role and not the owner of the page/data (e.g., an admin can edit anyone's page/data except his own -- say, to prevent an admin from deleting his own account)
  • No user is allowed to view this page, AllowAnyRolesIfNoneSpecified = false (e.g., you have a controller method for a page that doesn't exist but you need to include the method because your controller inherits from a base class that has this method)

这是一个示例属性声明:

Here's an example attribute declaration:

[AccountAuthorize(UserRole.Admin, MustBeInRoleAndNotPageOwner = true)]
public override ActionResult DeleteConfirmed(int id)
{
    ...
}

(这意味着管理员可以删除除他自己的任何帐户.)

(This means an admin can delete any account but his own.)

这篇关于在我的 ASP.NET MVC 站点中,我应该将访问权限的代码放在哪里?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆