Autorizar atributo con varios roles

97

Me gustaría agregar autorización a un controlador, para múltiples roles a la vez.

Normalmente, se vería así:

[Authorize(Roles = "RoleA,RoleB,RoleC")]
public async Task<ActionResult> Index()
{
}

Pero he almacenado mis roles en consts, ya que pueden cambiar o extenderse en algún momento.

public const RoleA = "RoleA";
public const RoleB = "RoleB";
public const RoleC = "RoleC";

No puedo hacer esto, ya que la cadena debe conocerse en el momento de la compilación:

[Authorize(Roles = string.join(",",RoleA,RoleB,RoleC)]
public async Task<ActionResult> Index()
{
}

¿Existe alguna forma de evitar el problema?

PODRÍA escribir una constante que simplemente contenga "RoleA, RoleB, RoleC", pero no me gustan las cadenas mágicas y esta es una cadena mágica. Cambiar el nombre de un rol y olvidarse de cambiar la cadena combinada sería un desastre.

Estoy usando MVC5. La identidad ASP.NET y el rol se conocen en tiempo de compilación.

Christian Sauer
fuente
¿Está usando public const string RoleA = "RoleA"; o como has escrito en cuestión?
Mukesh Modhvadiya

Respuestas:

188

Intente crear un atributo de autorización personalizado como este .

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

Suponiendo que sus roles serán los mismos para varios controladores, cree una clase auxiliar:

public static class Role
{
    public const string Administrator = "Administrator";
    public const string Assistant = "Assistant";
}

Entonces úsalo así:

public class MyController : Controller
{
    [AuthorizeRoles(Role.Administrator, Role.Assistant)]
    public ActionResult AdminOrAssistant()
    {                       
        return View();
    }
}
MacGyver
fuente
12
Esa es una idea digna de Mac Gyver;)
Christian Sauer
2
Muy buena solución :)
2015
1
También me gusta mucho esta solución, especialmente porque puedo dejar que mi rol sea una enumeración en lugar de una cadena. ¿Cuál sería un buen espacio de nombres y ubicación en la jerarquía del proyecto para colocar este atributo de autorización personalizado?
Simon Shine
4
No estoy seguro de lo que está sucediendo aquí, pero esto NO me ayudó, cualquier usuario, independientemente del rol, pudo acceder al método.
Urielzen
2
El mismo problema que @Urielzen, pero se solucionó con la respuesta a continuación de Jerry Finegan (usando "System.Web.Mvc.AuthorizeAttribute y NOT System.Web.Http.AuthorizeAttribute")
RJB
13

Asegúrese de derivar su clase de atributo personalizado desactivado System.Web.Mvc.AuthorizeAttributey NO System.Web.Http.AuthorizeAttribute.

Tuve el mismo problema. Una vez que lo cambié, todo funcionó.

También puede agregar lo siguiente a su clase de atributo personalizado:

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = true)] 
Jerry Finegan
fuente
Acabo de probar esto y encontré una referencia a la biblioteca System.Web.Http.AuthorizeAttributeEN LUGAR DESystem.Web.Mvc.AuthorizeAttribute
fraser jordan
10

La mejor y más sencilla forma que encontré para resolver este problema es simplemente concatenar roles en el atributo Authorize.

[Authorize(Roles = CustomRoles.Admin + "," + CustomRoles.OtherRole)]

con CustomRole una clase con cadenas constantes como esta:

public static class CustomRoles
{
    public const string Admin = "Admin";
    // and so on..
}
ChristopheHvd
fuente
2
Valioso; pero esto debería ser un comentario; no una respuesta.
GhostCat
1
¡Solución simple y elegante!
Iosif Bancioiu
Tanto su respuesta como la respuesta aceptada activarán la autorización si se implementan correctamente (estoy usando la aceptada en una aplicación web de producción). Proponer una edición para eliminar los comentarios sobre la respuesta aceptada.
Eric Eskildsen
3

Lo que hice es la respuesta en @Tieson

Modifico un poco su respuesta. En lugar de cadena. ¿Por qué no convertirlo en una lista?

Esta es mi respuesta:

public class AuthorizeRolesAttribute : AuthorizeAttribute
{
    private new List<string> Roles;
    public AuthorizeRolesAttribute(params string[] roles) : base()
    {
        Roles = roles.toList()
    }
}

Y luego verifique si el rol es válido anulando OnAuthorization

public override void OnAuthorization(HttpActionContext actionContext)
{
            if (Roles == null)
                HandleUnauthorizedRequest(actionContext);
            else
            {
                ClaimsIdentity claimsIdentity = HttpContext.Current.User.Identity as ClaimsIdentity;
                string _role = claimsIdentity.FindFirst(ClaimTypes.Role).Value;
                bool isAuthorize = Roles.Any(role => role == _role);

                if(!isAuthorize)
                    HandleUnauthorizedRequest(actionContext);
            }
        }

Y ahí lo tienes, ahora está validando si el rol está autorizado para acceder al recurso

Cristóbal Enríquez
fuente
1

Siento que un atributo de autorización personalizado es excesivo para este problema, a menos que tenga una gran cantidad de roles.

Dado que la cadena debe conocerse en el momento de la compilación, ¿por qué no crear una clase de rol estático que contenga cadenas públicas de los roles que ha definido y luego agregar cadenas separadas por comas con ciertos roles que desea autorizar?

public static class Roles
{
    public const string ADMIN = "Admin";
    public const string VIEWER = "Viewer";

    public const string ADMIN_OR_VIEWER = ADMIN + "," + VIEWER;
}

Y luego puede usar el atributo de autorización como tal en la clase de controlador o el método de controlador (o ambos):

[Authorize(Roles = Roles.ADMIN]
public class ExampleController : Controller
{
    [Authorize(Roles = Roles.ADMIN_OR_VIEWER)
    public ActionResult Create()
    {
        ..code here...
    }
}
Robert Tuttle
fuente
1
Este ejemplo no funciona, o al menos no de la forma en que podría pensar. Por ejemplo, si bien es novedoso, el ADMIN_OR_VIEWERrol en la acción es redundante porque no se le permitirá acceder al Createmétodo si aún no tiene el ADMINrol. En este caso VIEWER, nunca podrá invocar el Createmétodo.
John Leidegren
Esta solución tampoco es escalable. Habrá un punto en el que tendrás demasiados roles con diferentes acciones y no deberías crear todas las combinaciones
EduLopez