如何在 asp.net-mvc 控制器中集中授权逻辑?

Posted

技术标签:

【中文标题】如何在 asp.net-mvc 控制器中集中授权逻辑?【英文标题】:How can I centralize entitlement logic in an asp.net-mvc controller? 【发布时间】:2014-03-12 16:48:31 【问题描述】:

我有一个带有 SQL Server 后端的 ASP.NET-MVC 网站。我有许多需要我进行权利检查的控制器操作。

现在,我这样做:

    public ActionResult SomeEntitledPage()
    
        if (_myModel.IsMySiteAdminRole)
        
            return View(new MyViewModel());
        
        else
        
            return View("NotEntitled", new NotEntitledViewModel()Page = "[PageName]", SupportDG = "support@support.com");
        
    

这很好用,但感觉就像我在很多地方重复了这个逻辑。

根据以下内容,让多个权利控制器操作“安全”的最佳方式(属性等)是什么?

(安全的是它检查IsMySiteAdminRole,如果没有授权则返回“未授权”视图。

我还想确保我不会在每个页面上都有性能损失?

【问题讨论】:

基于您对以下每个人的答案的反对意见,尽管它们是最明显的答案,但我倾向于要求您展示您的视图模型,以便我可以看到您在其中所做的事情。这散发出设计的味道,因为下面的解决方案和 rbac 的想法一样都是教科书。这不应该那么复杂。 首先,_myModel 是从哪里来的?如果它是从浏览器发送的,那么它是错误的设计,任何人都可以覆盖从客户端发送的模型。如果 _myModel 是由 Controller 创建的,那么在创建控制器本身时,您可以抛出 UnauthorizedAccessException 并为其创建适当的错误页面。 @leora 以下回答了您的问题的人正在抽出宝贵的时间为您的问题提供深思熟虑的答案。拒绝那些试图帮助你的人是不礼貌的,除非他们提供了有害或愚蠢的答案。考虑到您的问题提供的细节很少,难怪他们提供的答案不符合您的需求? 【参考方案1】:

我更喜欢对权利/特权逻辑使用操作过滤器。这些过滤器的美妙之处在于它们可以在填充您的模型的操作方法之后运行。

例如:

public class AdminOnlyFilterAttribute : ActionFilterAttribute

      public override void OnActionExecuted(ActionExecutedContext filterContext)
      
        if (!filterContext.Controller.ViewData.Model.IsMySiteAdminRole)
            
                filterContext.Result = new ViewResult
                
                    ViewName = "NotEntitled",
                    Model = new NotEntitledViewModel()Page = "[PageName]", SupportDG = "support@support.com"
                ;
            
        base.OnActionExecuted(filterContext);
       

动作过滤器允许您选择性地覆盖控制器的OnActionExecuted 方法。

此属性可以应用于特定的操作或整个控制器。结果将取决于您的模型值,并且只会更改您的视图。

【讨论】:

【参考方案2】:

实现这一点的最佳方法是使用属性并用它来装饰动作。

看看System.Web.Mvc.AuthorizeAttribute

您可以继承它并执行您自己的自定义逻辑。这是我不久前做的一个项目的示例:

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = true)]
public class AuthorizeAttribute : System.Web.Mvc.AuthorizeAttribute

    public bool AdminRequired;
    protected override bool AuthorizeCore(HttpContextBase httpContext)
    
        if (UserSession.IsLoggedIn)
            return (!AdminRequired || (AdminRequired && UserSession.IsAdmin));
        else
            return false;
    

    protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
    
        filterContext.Result = new RedirectToRouteResult(
                                   new RouteValueDictionary 
                                   
                                        "action", "LogIn" ,
                                        "controller", "Account" ,
                                        "returnUrl", filterContext.HttpContext.Request.RawUrl
                                   );
    

使用此属性,您可以拥有两个级别的授权:普通用户和管理员用户。示例:

[Authorize(AdminRequired = true)]
public ActionResult AdminOnlyAction()

    // perform authorized admin tasks


[Authorize]
public ActionResult RegularUserAction()

    // perform authorized regular user action

【讨论】:

我只需要在 1 个特定的控制器中执行此操作(它引用了一个“模型”类,可以告诉我用户是否有权使用该功能。我将如何将其注入我可以在 AuthorizeCore 方法中调用“_myModel.IsMySiteAdminRole”吗? 我不认为放置在控制器动作上的属性可以知道动作的控制器。您可以尝试在传递的httpContext 中“寻找”它。我的建议是将_myModel.IsMySiteAdminRole 提取到一个知道HttpContext 的类中,您可以从属性中访问它。在我的示例中,UserSession 是一个静态类,其属性使用HttpContext.Current.Session 来存储当前用户的值。【参考方案3】:

我同意这里的其他人的观点,您可以使用属性来完成此操作,但我只想指出另一种选择。您可以编写一个基本控制器,所有需要权利检查的控制器都从该控制器继承。在该控制器中,您可以编写逻辑来检查权利:

public class BaseController : Controller

    private readonly bool _isEntitled;

    protected override void Initialize(RequestContext requestContext)
    
        base.Initialize(requestContext);
        // Your logic for entitlement check goes here.
        // Set _isEntitled to true if user is entitled to view page.
    

    protected override void OnActionExecuting(ActionExecutingContext filterContext)
    
        if (!_isEntitled) 
           // Redirect user which is not entitled.
           filterContext.Result = new ViewResult
                                  
                                     ViewName = "NotEntitled",
                                     Model = new NotEntitledViewModel()Page = "[PageName]", SupportDG = "support@support.com"
                                  ;
        
    

现在您要做的就是在所有需要进行权利检查的控制器中继承此控制器:

  public MyController : BaseController
  
      // Action in here...
  

我并不是说这是一个更好的选择,我只是指出另一种方法来做你需要的事情。此外,您还可以实现某种缓存,以确保权利检查不会发生在每个页面请求上,而是仅在用户登录时发生一次......

【讨论】:

我不希望每个控制器中的每个操作都被授权。 .它只是一些控制器中的一些动作。您的答案将如何满足这一要求?【参考方案4】:

TL;DR: 考虑扩展您的用户配置文件并在用户通过身份验证时填充角色信息。


对我来说,这听起来问题源于安全层的设计,因为您正在检查的用户仅在导致此复杂性的错误执行阶段可用。 看起来您正在尝试做的是检查发出请求的用户,看看他是否满足特定的角色要求,如果他们不满足要求,则向他们显示未经授权的页面(漂亮的教科书内容) ),但由于某种原因,该用户位于视图模型中,并且在控制器完全实例化之前不会构建该视图模型,这意味着您无法检查它是否已在管道中初始化。马虎的解决方案是覆盖控制器上的初始化方法,调用基本方法,然后在基本初始化完成后做一些工作。然后你的数据应该准备好了。但这很草率。

一般来说,您的会话用户应该以 UserProfile 或扩展身份的形式在会话中可用。通过这样做,您可以填充该用户的角色以及不填充的内容,然后在控制器/动作执行管道的任何阶段进行检查;那么您可以使用自定义属性来检查 User.IsInRole("Whatever")。您不需要该操作来返回“未授权”视图,因为您可以在授权属性 HandlUnauthorizedRequest 覆盖的响应中设置它。

【讨论】:

我同意你的许多观点。我看到了你之前的赏金答案。在这种情况下,作为这个,您超出了问题范围并将 OP 重新定向到“更好”的设计。我真的很喜欢这个答案。一般来说(在这种情况下),我畏缩向 OP “宣扬”他们的方法是否正确,b/c 开发人员经常做出我们无法欣赏或继承 reqs / app 组件的选择和权衡。 【参考方案5】:

我同意其他人建议的解决方案!!

您提到的是,您需要在需要检查模型 IsInAdminRole 属性的任何地方复制代码。所以我创建了一个通用方法,您可以在其中传递模型和视图名称/路径。在 IsEntitled 方法中,只需检查 isadminrole 属性并采取必要的措施。

注意:这只是一个简单的示例解决方案。您可以检查这是否有帮助或提供一些指导。

按以下方式做。

示例模型

public class NoRights

    public string Message  get; set; 


public class MyModel

    public bool IsAdminRole  get; set; 

这是家庭控制器

public class HomeController : Controller


    public ActionResult Index()
    
        var mod = new MyModel()  IsAdminRole = true ;
        return IsEntitled(mod, "IndeX");
        //return View();
    

这是一个示例静态方法。这可以进入帮助类。 注意:这将返回一个标准错误视图,其中包含在 NoRights 模型的 Message 属性中指定的错误消息。

    public static ViewResult IsEntitled(object model, string viewPath)
    
        var prop = model.GetType().GetProperty("IsAdminRole");
        var hasRights = (bool)prop.GetValue(model, null);
        var viewResult = new ViewResult();
        if (hasRights)
        
            viewResult.ViewData = new ViewDataDictionary(model);
            viewResult.ViewName = viewPath;
        
        else
        
            viewResult.ViewData = new ViewDataDictionary(
                new NoRights()  Message = "Your dont have rights" );
            viewResult.ViewName = "Error";

        
        return viewResult;
    

如果我在 IsAdminRole 属性中传递 true,那么这里就是输出。

如果我在 IsAdminRole 属性中传递 false,我会得到以下输出。

希望这会有所帮助。

【讨论】:

我认为您在封装 OP 的逻辑方面做得很好。但我的印象是 OP 正在寻找远离动作方法主体的抽象工作。【参考方案6】:

我们有一个类似的案例,其中要求将授权逻辑完全设置在一个集中位置。为此,您可以尝试类似的方法。

首先你可以有一个应用层控制器,它可以是

public class ApplicationController : Controller

    protected override void OnActionExecuted(ActionExecutedContext ctx)
    
        base.OnActionExecuted(ctx);

        if (!_myModel.IsMySiteAdminRole)
        
            ctx.Result = View("NotEntitled", new NotEntitledViewModel()Page = "[PageName]", SupportDG = "support@support.com");
        
    

现在这个控制器可以被其他控制器继承。您的整个权利逻辑现在都集中在一个控制器中。

public class EntitlementController: ApplicationController

    return View(new MyViewModel());

【讨论】:

以上是关于如何在 asp.net-mvc 控制器中集中授权逻辑?的主要内容,如果未能解决你的问题,请参考以下文章

在 ASP.Net-MVC 中的控制器之间传递信息

剑道 DropDownListFor() 与 ASP.NET-MVC

模拟 Asp.net-mvc 控制器上下文

在 asp.net-mvc 中从服务器读取文本文件的最佳方法是啥

在 asp.net-mvc 中,在不影响其他用户的情况下进行昂贵操作的正确方法是啥?

有人愤怒地使用带有 asp.net-mvc 的 Knockoutjs 吗? [关闭]