允许多个角色访问控制器操作

327

现在我这样装饰一个方法,以允许“成员”访问我的控制器操作。

[Authorize(Roles="members")]

我如何允许多个角色访问?例如,以下代码不起作用,但它展示了我的意图(允许“成员”和“管理员”访问):

[Authorize(Roles="members", "admin")] 

4
请将此问题的已接受答案更改为新回答。目前接受答案的人已编辑回答,表明他是错误的。 - Eric J.
13个回答

679

另一种选项是使用一个授权过滤器,就像你发布的那样,但去掉内部引号。

[Authorize(Roles="members,admin")]

5
MVC 5 中也适用。+1 - gkonuralp
5
适用于ASP.NET Core 1.0(MVC 6)和Microsoft.AspNet.Identity v3.*。 - Soren
4
如果您只需要授权一个控制器,这样做就可以了。但如果您有多个控制器,那么您会重复使用那些字符串常量(很糟糕)。我更喜欢拥有角色名称的静态类。我最讨厌的是重复的字符串...非常糟糕。 - robnick
2
为什么?我花了很长时间才解决这个问题。这可能对其他遇到同样问题的人有所帮助。 - Craig
3
根据 https://learn.microsoft.com/en-us/aspnet/core/security/authorization/roles?view=aspnetcore-6.0#adding-role-checks,此处为`OR`,而非`AND`。 - gawkface
显示剩余6条评论

161
如果您想使用自定义角色,可以这样做: CustomRoles 类:
public static class CustomRoles
{
    public const string Administrator = "Administrador";
    public const string User = "Usuario";
}

使用方法

[Authorize(Roles = CustomRoles.Administrator +","+ CustomRoles.User)]

如果你只有少量角色,或许可以将它们进行合并(以增加清晰度),方法如下:

public static class CustomRoles
{
     public const string Administrator = "Administrador";
     public const string User = "Usuario";
     public const string AdministratorOrUser = Administrator + "," + User;  
}

使用方法

[Authorize(Roles = CustomRoles.AdministratorOrUser)]

10
如果你向不知道CustomRoles背后含义的人解释清楚,那么这将是一个很好的答案。 - James Skemp
1
@JamesSkemp 好的,我已经扩展了我的答案。这很简单。CustumRoles是我创建的一个类,其中包含一些常量,对应于我的应用程序角色。我这样做有几个原因:1)它允许使用智能感知来避免拼写错误2)简化维护。如果角色更改,我只需要在应用程序中更新一个地方。 - Pablo Claus
@Pabloker 或者你可以创建一个带有 Flags 属性的枚举,例如 Convert.ToString(CustomRoles.Administrator | CustomRoles.User);烦人的部分是这需要显式转换。 - cstruter
如果你有39个角色? - Kiquenet
我认为你的问题涉及到许可建模,超出了使用.NET所能做的范围。 - Pablo Claus

105

有一个可能的简化方式是通过子类化 AuthorizeAttribute

public class RolesAttribute : AuthorizeAttribute
{
    public RolesAttribute(params string[] roles)
    {
        Roles = String.Join(",", roles);
    }
}

使用方法:

[Roles("members", "admin")]

在语义上,它与Jim Schmehil的答案相同。


4
这对我没用,已登录的用户即使没有任何角色也可以绕过该属性。 - Urielzen
13
当您使用常量作为值时,这个答案更好:即 [Roles(Constants.Admin, Constants.Owner)]。 - dalcam
5
这是最好的答案。 - IgorShch
4
我知道这是一篇旧帖子,但是让我补充一下。为了使这段代码片段工作,您必须在System.Web.Mvc.Controllers中使用System.Web.Mvc.AuthorizeAttribute,并在System.Web.Http.ApiController(RESTful调用)中使用System.Web.Http.AuthorizeAttribute - Efthymios

19
对于MVC4,我使用一个拥有角色的自定义AuthorizeAttribute,其中包含一个EnumUserRoles)。
在我的控制器操作中,我执行以下操作:
[CustomAuthorize(UserRoles.Admin, UserRoles.User)]
public ActionResult ChangePassword()
{
    return View();
}

我使用一个自定义的AuthorizeAttribute,像这样:

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = true)]
public class CustomAuthorize : AuthorizeAttribute
{
    private string[] UserProfilesRequired { get; set; }

    public CustomAuthorize(params object[] userProfilesRequired)
    {
        if (userProfilesRequired.Any(p => p.GetType().BaseType != typeof(Enum)))
            throw new ArgumentException("userProfilesRequired");

        this.UserProfilesRequired = userProfilesRequired.Select(p => Enum.GetName(p.GetType(), p)).ToArray();
    }

    public override void OnAuthorization(AuthorizationContext context)
    {
        bool authorized = false;

        foreach (var role in this.UserProfilesRequired)
            if (HttpContext.Current.User.IsInRole(role))
            {
                authorized = true;
                break;
            }

        if (!authorized)
        {
            var url = new UrlHelper(context.RequestContext);
            var logonUrl = url.Action("Http", "Error", new { Id = 401, Area = "" });
            context.Result = new RedirectResult(logonUrl);

            return;
        }
    }
}

这是 Fabricio Martínez Tamayo 修改后的 FNHMVC 的一部分 https://github.com/fabriciomrtnz/FNHMVC/


1
您的 OnAuthorization 方法将要求用户拥有所有枚举角色; 这是有意为之,还是在循环中缺少了一个 break? - Tieson T.
@Tieson:我仔细检查了一下,这个循环明显需要一个break。 - OcelotXL
@TiesonT和@madrush,感谢你们的修复,循环内部确实可能会出现中断。我将更改上面的代码。 - Bernardo Loureiro
枚举类型UserRoles不错。你是手动声明还是根据数据库内容自动生成的? - Konrad Viltersten
@KonradViltersten 这是手动的,但我猜想可以通过反射和动态类自动生成来完成。 - Bernardo Loureiro
覆盖 OnAuthorization 是一个不好的想法。有一些代码可以防止缓存页面在原始页面中被提供。请检查源代码。 - Worthy7

10

您可以在Startup.cs中使用授权策略(Authorization Policy)

    services.AddAuthorization(options =>
    {
        options.AddPolicy("admin", policy => policy.RequireRole("SuperAdmin","Admin"));
        options.AddPolicy("teacher", policy => policy.RequireRole("SuperAdmin", "Admin", "Teacher"));
    });

在控制器文件中:

 [Authorize(Policy = "teacher")]
 [HttpGet("stats/{id}")]
 public async Task<IActionResult> getStudentStats(int id)
 { ... }

"教师"政策接受3个角色。


3
是AND还是OR? - variable

4

使用AspNetCore 2.x,您需要采取一些不同的方式:

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = true)]
public class AuthorizeRoleAttribute : AuthorizeAttribute
{
    public AuthorizeRoleAttribute(params YourEnum[] roles)
    {
        Policy = string.Join(",", roles.Select(r => r.GetDescription()));
    }
}

只需按照以下方式使用:

[Authorize(YourEnum.Role1, YourEnum.Role2)]

策略 = 还是 角色 =? - SerjG
Roles = string.Join(",", roles.Select(r => r.ToString())); 角色 = string.Join(",", 角色.Select(r => r.ToString())); - Caner

4

我混合了回答并提出了这种方法。

首先,我们为角色访问创建一个枚举。

public enum ERoleAccess
{
     [Description("Admin User")]
     Admin = 1,

     [Description("General User")]
     User = 2,

     [Description("Editor User")]
     Editor = 3,
}

其次,我们需要一个针对客户MVC授权的属性过滤器。
public class RolesAttribute:AuthorizeAttribute
{
   public RolesAttribute(params ERoleAccess[] roles)
   {
      Roles = string.Join(",", roles);
   }
}

最后,我们可以在控制器或操作上使用“RolesAttribute”。


[Roles(ERoleAccess.Admin, ERoleAccess.Editor, ERoleAccess.User)]

在这种方法中,我们使用多个替代字符串值的编号。(1=管理员,2=用户,...)

这有助于减小令牌大小并提高比较性能。


目前你的回答不够清晰,请编辑并添加更多细节,以帮助其他人理解它如何回答问题。你可以在帮助中心找到有关如何编写好答案的更多信息。 - Community

4

另一个清晰的解决方案是,您可以使用常量来保持约定并添加多个 [Authorize] 属性。看一下这个:

public static class RolesConvention
{
    public const string Administrator = "Administrator";
    public const string Guest = "Guest";
}

然后在控制器中:
[Authorize(Roles = RolesConvention.Administrator )]
[Authorize(Roles = RolesConvention.Guest)]
[Produces("application/json")]
[Route("api/[controller]")]
public class MyController : Controller

23
多个 Authorize 属性使用 AND 语义,并要求满足所有条件(即用户必须同时具备管理员和访客角色)。 - trousyt

3

通过添加子类AuthorizeRole.cs改进代码

    [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = true)]
    class AuthorizeRoleAttribute : AuthorizeAttribute
    {
        public AuthorizeRoleAttribute(params Rolenames[] roles)
        {
            this.Roles = string.Join(",", roles.Select(r => Enum.GetName(r.GetType(), r)));
        }
        protected override void HandleUnauthorizedRequest(System.Web.Mvc.AuthorizationContext filterContext)
        {
            if (filterContext.HttpContext.Request.IsAuthenticated)
            {
                filterContext.Result = new RedirectToRouteResult(
                new RouteValueDictionary {
                  { "action", "Unauthorized" },
                  { "controller", "Home" },
                  { "area", "" }
                  }
              );
                //base.HandleUnauthorizedRequest(filterContext);
            }
            else
            {
                filterContext.Result = new RedirectToRouteResult(
                new RouteValueDictionary {
                  { "action", "Login" },
                  { "controller", "Account" },
                  { "area", "" },
                  { "returnUrl", HttpContext.Current.Request.Url }
                  }
              );
            }
        }
    }

如何使用这个?
[AuthorizeRole(Rolenames.Admin,Rolenames.Member)]

public ActionResult Index()
{
return View();
}

3
如果你经常使用这两个角色,你可以将它们包装在自己的授权中。这实际上是接受答案的扩展。
using System.Web.Mvc;

public class AuthorizeAdminOrMember : AuthorizeAttribute
{
    public AuthorizeAdminOrMember()
    {
        Roles = "members, admin";
    }
}

然后将您的新授权应用于操作。我认为这看起来更清晰,阅读起来更容易。

public class MyController : Controller
{
    [AuthorizeAdminOrMember]
    public ActionResult MyAction()
    {
        return null;
    }
}

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