ASP.NET MVC中如何为整个区域设置授权?

64

我有一个管理区域,我只希望管理员能进入该区域。我考虑在管理区域的每个控制器中添加Authorized属性。是否有更优雅的解决方案或者框架本身没有这个功能?

编辑: 很抱歉,我之前应该提到。我正在使用从AuthorizeAttribute派生出来的自定义AuthorizedAttribute。


请查看我的博客文章保护你的ASP.NET MVC 3应用程序 - RickAndMSFT
请查看我的博客文章“如何保护您的ASP.NET MVC 4应用程序”以及新的“AllowAnonymous属性”。 - RickAndMSFT
Rick的最后评论链接-> http://blogs.msdn.com/b/rickandy/archive/2012/03/23/securing-your-asp-net-mvc-4-app-and-the-new-allowanonymous-attribute.aspx - Peter B
8个回答

56

基于 Web.config 的安全性在 MVC 应用程序中几乎永远不应该被使用。原因是多个 URL 可能会命中一个控制器,在 Web.config 中设置这些检查通常会漏掉某些情况。请记住 - 控制器与区域无关,路由与区域相关联。如果没有冲突,MVC 控制器工厂将很高兴地为非区域请求提供 Areas/ 文件夹下的控制器。

例如,使用默认项目结构,在 AdminDefaultController 中添加一个 Admin 区域,您可以通过 /Admin/AdminDefault/Index 和 /AdminDefault/Index 两种方式访问该控制器。

唯一支持的解决方案是将属性放在控制器基类上,并确保该区域内的每个控制器都是该基类的子类。


1
有没有一种好的方法可以确保控制器只通过单个URL访问?也许通过正确规划路由? - Cédric Rup
2
无法确保控制器仅通过单个URL可访问。路由只是访问控制器的机制之一,而不是唯一的机制。这就是为什么任何安全属性都需要直接应用于控制器本身,而不是路由(以及区域)。例如,在MVC 3中引入MvcHandler.ashx。这将通过绕过所有路由直接调用MVC框架。 - Levi
1
正确。始终假设每个控制器的公共方法都可以被全世界调用。因此,[Authorize]、[NonAction]和其他属性是保护这些资源的正确方式。 - Levi
8
更明智的方法是提供一种机制,可以强制在给定区域中的所有控制器上应用过滤器,而不考虑你的论点。开发者更容易忘记添加属性。这就像系统人员建议他们的用户逐个安全地保护文件夹中的每个文件,因为Windows防止系统人员在文件夹级别上执行此操作。在我看来,这是另一种不成熟的想法。为了仁慈起见,请保持DRY(不要重复自己)! - jenson-button-event
1
来自未来的问候- 属性路由是一种更好的方式来控制你的路由,以更细粒度和更容易理解的方式,且很少会出现多个随机路由到相同页面的情况。强烈推荐。 - niico
显示剩余3条评论

49

我刚刚调查了这个同样的问题。由于无法根据区域来保护控制器,因此想到了一个更简单的选择。

为每个区域创建一个基本控制器定义来覆盖Controller,并将安全性要求添加到此控制器中。然后,您只需要确保该区域中的每个控制器都是继承自AreaController而不是Controller即可。例如:

/// <summary>
/// Base controller for all Admin area
/// </summary>
[Authorize(Roles = "Admin")]
public abstract class AdminController : Controller { }

仍需要从此基础派生Admin区域中的每个控制器,

public class HomeController : AdminController
{
    // .. actions
}

但至少你有一个单一的点来定义该区域的安全性。


2
是的,那听起来确实是个好主意。此外MSDN还建议了其他问题的类似解决方案。继承很好。我喜欢这个答案。 - Piotr Kula
1
我真的不明白为什么你要在管理部分中对每个控制器进行子类化,而不是简单地在类定义上方编写你的一行属性。 - Gudradain
4
简单的回答是DRY - http://en.wikipedia.org/wiki/Don't_repeat_yourself - 我可以在一行代码中更改受保护的角色,而不必寻找每个[Authorize]属性。 - Quango
这对我来说看起来非常干净。但是如果我旁边的任何开发人员忘记在其区域内的新控制器中继承AdminController怎么办?接下来,当我在我的实现(identityserver3)中添加此内容并且用户直接访问区域控制器时,我会看到身份验证服务器登录页面上一直循环出现问题。 - Abhimanyu
3
开发人员忘记从基本控制器继承的问题与开发人员忘记向控制器添加[Authorize]属性的问题相同。解决方案在这里并没有错。 - Quango

18

我刚开始做这个...但目前来看,这对我而言运作得相当不错。

我创建了一个自定义的AuthorizeAttribute类,并将其添加到RegisterGlobalFilters函数中。

在CustomAuthorizeAttribute中,我根据它所在的区域检查各种条件。

public class FilterConfig
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(new CustomAuthorizeAttribute());
        filters.Add(new HandleErrorAttribute());
    }
}

public class CustomAuthorizeAttribute : AuthorizeAttribute
{
    protected override bool AuthorizeCore(HttpContextBase httpContext)
    {
        var routeData = httpContext.Request.RequestContext.RouteData;
        var controller = routeData.GetRequiredString("controller");
        var action = routeData.GetRequiredString("action");
        var area = routeData.DataTokens["area"];
        var user = httpContext.User;
        if (area != null && area.ToString() == "Customer")
        {
            if (!user.Identity.IsAuthenticated)
                return false;
        }
        else if (area != null && area.ToString() == "Admin")
        {
            if (!user.Identity.IsAuthenticated)
                return false;
            if (!user.IsInRole("Admin"))
                return false;
        }
        return true;
    }
}

12

如果您所有的管理员代码都在一个控制器中,则将Authorize添加到整个类中。

[Authorize]
public class AdminController : Controller
{
     .......
}

2
这对于单个控制器很有效。但是我们如何为整个区域做到这一点? - Piotr Kula
@ppumkin, AuthorizeAttributeInherited 属性已设置为 true,因此只需继承它。https://msdn.microsoft.com/zh-cn/library/system.web.mvc.authorizeattribute(v=vs.118).aspx - Mikael Dúi Bolinder

9
目前被接受的答案并不是最安全的解决方案,因为它要求开发者在任何新的控制器或操作中始终记得继承新的基类(“黑名单”;允许用户访问所有内容,除非手动限制操作)。当新开发人员加入项目时,这特别会引起问题,因为他们不熟悉您的惯例。以这种方式进行操作时很容易忘记继承正确的控制器类,尤其是在数周、数月或数年后再次关注该项目之后。如果开发者忘记继承,那么在项目中存在安全漏洞就不明显了。
更安全的解决方案是拒绝所有请求,然后使用允许访问操作的角色对每个操作进行装饰(“白名单”;防止所有用户访问除非手动允许)。现在,即使开发人员忘记给适当的授权添加白名单,用户也会提醒你,只需要查看其他控制器以获得有关如何给出适当访问权限的提示即可。但是,至少没有重大的安全漏洞。
在App_Start/FilterConfig.cs文件中修改FilterConfig类:
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        ...

        //Deny access to all controllers and actions so that only logged in Administrators can access them by default
        filters.Add(new System.Web.Mvc.AuthorizeAttribute() { Roles = "Administrator" });
    }

这将使所有操作都无法访问,除非用户以管理员身份登录。然后,对于每个你想授权给不同授权用户访问的操作,你只需用[OverrideAuthorization][Authorize]进行装饰。
在你的业务逻辑中,这允许你以各种方式使用Authorize属性,而不需要担心未经授权的用户访问任何功能。以下是一些示例。 示例1 - 只有已登录的管理员和调度员用户才能访问Index()的Get和Post方法。
public class MarkupCalculatorController : Controller //Just continue using the default Controller class.
{
    // GET: MarkupCalculator
    [OverrideAuthorization]
    [Authorize(Roles = "Administrator,Dispatcher")]
    public ActionResult Index()
    {
        //Business logic here.

        return View(...);
    }

    // POST: DeliveryFeeCalculator
    [HttpPost]
    [ValidateAntiForgeryToken]
    [OverrideAuthorization]
    [Authorize(Roles = "Administrator,Dispatcher")]
    public ActionResult Index([Bind(Include = "Price,MarkedupPrice")] MarkupCalculatorVM markupCalculatorVM)
    {
        //Business logic here.

        return View(...);
    }
}

示例2 - 仅允许经过身份验证的用户访问Home控制器的Index()方法。

public class HomeController : Controller
{
    [OverrideAuthorization]
    [Authorize] //Allow all authorized (logged in) users to use this action
    public ActionResult Index()
    {
        return View();
    }

}

示例 3 - 可以使用 [AllowAnonymous] 属性允许未经身份验证的用户(即匿名用户)访问方法。这也会自动覆盖全局过滤器,无需使用 [OverrideAuthorization] 属性。

    // GET: /Account/Login
    [AllowAnonymous]
    public ActionResult Login(string returnUrl)
    {
        ViewBag.ReturnUrl = returnUrl;
        return View();
    }

    //
    // POST: /Account/Login
    [HttpPost]
    [AllowAnonymous]
    [ValidateAntiForgeryToken]
    public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
    {
        ...
    }

示例 4 - 只有管理员才能访问缺少 [Authorize] 属性的方法。

public class LocationsController : Controller
{

    // GET: Locations
    public ActionResult Index()
    {
        //Business logic here.
        return View(...);
    }
}

一些注意事项。

如果您想限制特定角色访问特定操作,则必须使用[OverrideAuthorization]属性。否则,[Authorize]属性将被忽略,只允许默认角色(例如我的示例中的管理员),即使您指定其他角色(例如Dispatcher等),也因为全局过滤器而被允许。任何未经授权的用户都将被重定向到登录屏幕。

使用[OverrideAuthorization]属性会导致操作忽略您设置的全局过滤器。因此,每次使用覆盖时,您必须重新应用[Authorize]属性,以确保操作的安全性。

关于整个区域和控制器

如您所询问的,要限制区域,请在控制器上放置[OverrideAuthorization][Authorize]属性,而不是单个操作。


1
在startup.cs中,使用区域名称和斜杠与AuthorizeAreaFolder一起使用对我很有帮助:
services.AddRazorPages()
        .AddRazorPagesOptions(options => options.Conventions.AuthorizeAreaFolder("Admin", "/"))
        .WithRazorPagesAtContentRoot();

如何设置Areas/Identity/Pages/Account/Manage文件夹?options.Conventions.AuthorizeAreaFolder("Identity", "/Manage","AdminPolicy");无效。 - مهدی

0
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("AdminPolicy", policy => policy.RequireRole("Administrator"));
});
builder.Services.AddRazorPages(options =>
{
options.Conventions.AuthorizeAreaFolder("Identity","/Account/Manage", "AdminPolicy");
});

-5

..我相信你想要的是类似这样的东西吧?

快速而简单的角色管理

[Authorize(Roles = "Admins")]
public ActionResult Register()
{
  ViewData["roleName"] = new SelectList(Roles.GetAllRoles(), "roleName");
  ViewData["PasswordLength"] = MembershipService.MinPasswordLength;
  return View();
}

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