2010-05-13 29 views
25

ASP.NET MVC tiene un buen soporte para la seguridad basada en roles, pero el uso de cadenas como nombres de roles es enloquecedor, simplemente porque no se pueden escribir fuertemente como enumeraciones.Nombres de roles sin cadena en ASP.NET MVC?

Por ejemplo, tengo una función de "Administrador" en mi aplicación. La cadena "Admin" ahora existirá en el atributo Authorize de mi acción, en mi página maestra (para ocultar una pestaña), en mi base de datos (para definir las funciones disponibles para cada usuario) y en cualquier otro lugar de mi código o vista archivos en los que necesito realizar una lógica especial para usuarios administradores o no administradores.

¿Existe una solución mejor, sin escribir mi propio atributo de autorización y filtro, que quizás se ocupe de una colección de valores de enumeración?

Respuesta

19

Normalmente utilizo una clase con un montón de constantes de cadena. No es una solución perfecta, ya que debe recordar seguir utilizando en todas partes, pero al menos elimina la posibilidad de errores tipográficos.

static class Role { 
    public const string Admin = "Admin"; 
} 
+0

Fui con esta solución debido a su simplicidad. Los cambios de código fueron mínimos, ya que solo tuve que reemplazar cadenas codificadas con referencias constantes. – MikeWyatt

3

No es que difícil de personalizar AuthorizeAttribute en la forma en que usted sugiere.

Subtípelo, agregue una propiedad personalizada para su tipo de enumeración y llame al ToString() en el valor aprobado. Ponlo en la propiedad de roles regulares. Esto debería tomar solo algunas líneas de código, y AuthorizeAttribute todavía hace todo el trabajo real.

+1 para Matti, también, ya que las constelaciones también son una buena opción.

2

He utilizado una clase estática que define un conjunto de constantes de cadena como lo sugiere Matti y en mi proyecto actual utilizo el método de extensión siguiente con una enumeración. Ambos enfoques funcionan muy bien.

public static class EnumerationExtension 
{ 
    public static string GetName(this Enum e) 
    { 
    return Enum.GetName(e.GetType(), e); 
    } 
} 
46

El uso de cuerdas mágicas le da la flexibilidad para declarar múltiples funciones en el atributo Autorizar (por ejemplo, [autorice (Roles = "Administrador, Moderador")], que tiende a perder a medida que avanza a una solución inflexible. pero aquí es cómo se puede mantener esta flexibilidad al mismo tiempo conseguir todo lo inflexible de tipos

Definir sus papeles en una enumeración que utiliza indicadores de bits:.

[Flags] 
public enum AppRole { 
    Admin = 1, 
    Moderator = 2, 
    Editor = 4, 
    Contributor = 8, 
    User = 16 
} 

Anulación AuthorizeAttribute:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] 
public class MyAuthorizeAttribute : AuthorizeAttribute { 

    public AppRole AppRole { get; set; } 

    public override void OnAuthorization(AuthorizationContext filterContext) { 
     if (AppRole != 0) 
      Roles = AppRole.ToString(); 

     base.OnAuthorization(filterContext); 
    } 

} 

Ahora si se puede usar MyAuthorizeAttribute así:

[MyAuthorize(AppRole = AppRole.Admin | AppRole.Moderator | AppRole.Editor)] 
public ActionResult Index() { 

    return View(); 
} 

La acción anterior sólo se autorizará a los usuarios que se encuentran en al menos una de las funciones enumeradas (Administrador, Moderador, o el editor). El comportamiento es el mismo que el atributo AuthorizeAttribute predeterminado de MVC, excepto sin las cadenas mágicas.

Si se utiliza esta técnica, aquí está un método de extensión en IPrincipal que también pueden ser útiles:

public static class PrincipalExtensions { 

    public static bool IsInRole(this IPrincipal user, AppRole appRole) { 

     var roles = appRole.ToString().Split(',').Select(x => x.Trim()); 
     foreach (var role in roles) { 
      if (user.IsInRole(role)) 
       return true; 
     } 

     return false; 
    } 
} 

Usted puede utilizar este método de extensión de esta manera:

public ActionResult Index() { 
    var allowed = User.IsInRole(AppRole.Admin | AppRole.Moderator | AppRole.Editor); 

    if (!allowed) { 
     // Do Something 
    } 

    return View(); 
} 
+0

Me gusta mucho este enfoque, es fácil de implementar y mantener – Sam

+0

@Jammer, los valores enum no necesariamente tienen que coincidir con los ID de la base de datos. Pueden ser independientes y aún funcionan igual de bien. –

+0

Sí, me di cuenta de eso justo cuando comencé a usarlo. Tonto ... – Jammer

10

A pesar de que no lo hace use enumeraciones, he usado la solución a continuación, donde subclasificamos el filtro Autorizar para tomar argumentos de nombre de rol de longitud variable en el constructor. El uso de este junto con los nombres de función declarados en las variables const en alguna parte, evitamos cuerdas mágicas:

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

public class MyController : Controller 
{ 
    private const string AdministratorRole = "Administrator"; 
    private const string AssistantRole = "Assistant"; 

    [AuthorizeRoles(AdministratorRole, AssistantRole)] 
    public ActionResult AdminOrAssistant() 
    {       
     return View(); 
    } 
} 

(I escribió sobre esto en un poco más detalle - http://tech-journals.com/jonow/2011/05/19/avoiding-magic-strings-in-asp-net-mvc-authorize-filters)

+0

¿Cómo personalizarías esto aún más haciéndolo funcionar o delegar? es decir, user => user.Role == AssistantRole || user.Role == BigGuy ... Algunas acciones pueden querer un rol y no el otro, algunos pueden querer 2 roles o un tercer rol, espero que esté claro ??? :) – Haroon

3

Tomé la respuesta de JohnnyO pero cambió la enumeración elementos para usar el DescriptionAttribute para especificar el valor de cadena para el rol. Esto es útil si quieres que tu cadena de roles sea diferente del nombre de Enum.

El ejemplo de enumeración:

[Flags] 
public enum AppRole 
{ 
    [Description("myRole_1")] 
    RoleOne = 1, 
    [Description("myRole_2")] 
    RoleTwo = 2 
} 

El método de extensión:

public static bool IsInRole(this IPrincipal user, AppRole appRole) 
{ 
    var roles = new List<string>(); 
    foreach (var role in (AppRole[])Enum.GetValues(typeof(AppRole))) 
     if ((appRole & role) != 0) 
      roles.Add(role.ToDescription()); 

    return roles.Any(user.IsInRole); 
} 

El atributo personalizado:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] 
public class AppAuthorizeAttribute : AuthorizeAttribute 
{ 
    public AppRole AppRoles { get; set; } 

    public override void OnAuthorization(AuthorizationContext filterContext) 
    { 
     var roles = new List<string>(); 
     foreach (var role in (AppRole[])Enum.GetValues(typeof(AppRole))) 
      if((AppRoles & role) != 0) 
       roles.Add(role.ToDescription()); 

     if (roles.Count > 0) 
      Roles = string.Join(",", roles); 

     base.OnAuthorization(filterContext); 
    } 
} 

método de extensión para obtener el valor Descripción:

public static string ToDescription(this Enum value) 
{ 
    var da = (DescriptionAttribute[]) 
      (value.GetType().GetField(value.ToString())) 
       .GetCustomAttributes(typeof (DescriptionAttribute), false); 
    return da.Length > 0 ? da[0].Description : value.ToString(); 
} 
Cuestiones relacionadas