2010-12-05 14 views
8

No me gustan los proveedores de membresía integrados. He decidido lanzar el mío. Estoy tratando de encontrar un buen método para realizar la autorización en el nivel de acción. Estos son los requisitos que estoy tratando ir por:Permisos granulares con ciertos requisitos para un sitio de MVC

  1. atributo de uso - Me gusta este ya que controla a un nivel muy alto en la pila de llamadas y es un buen lugar para organizar los permisos.
  2. Sin cadenas mágicas: esta es una razón por la que me estoy alejando de los proveedores de roles actuales. No quiero dejar cadenas que no puedan renombrarse fácilmente.
  3. Los permisos se pueden componer de uno otro permiso. Ejemplo: ReadWrite tiene permiso para Read. Al igual que or'ing con una enumeración.

NOTA: Algunos piensan que este conjunto de requisitos es demasiado amplio (ver comentarios). No lo creo, creo que son bastante sencillos.

El mayor inconveniente es el uso de atributos. Solo puede haber "expresiones constantes, expresiones typeof o expresión de creación de matriz de un tipo de parámetro de atributo".

Estaba pensando en tal vez tener algo como esto para que las operaciones tengan acceso estático. Dentro del atributo, sería "convertir" el int con el permiso real o algo ...:

public static class Operations 
{ 
    public static class SectionA 
    { 
     public const int Read = 1; 
     public const int ReadWrite = 2; 
    } 

    public static class SectionB 
    { 
     // ... and so on... 
    } 
} 

Pero lo que realmente limita composición. Estoy seguro de que estás pensando "¿por qué no vas por la ruta enum?" bueno, quiero planear que las cosas cambien y no quiero limitarme a 32 (int) o 64 (long) operaciones y tengo que hacer una reescritura masiva más tarde (también en el DB que es feo).

Además, si hay una mejor alternativa que los atributos en las acciones/controladores, entonces soy todo oídos para recibir sugerencias.

EDITAR: También de this post, he leído sobre la clase BitArray. Parece tipo de feo, especialmente con el almacenamiento arbitrario en la base de datos.

+0

¿No es este tipo de amplio? – jfar

+0

@jfar - ¿Cómo es esto amplio ...? – TheCloudlessSky

+0

Bueno, primero es contradictorio. Usted dice que debe usar atributos y luego pide alternativas de atributos. Luego dices que necesitas usar atributos, pero no quieres cadenas mágicas o enumeraciones, así que estás muy limitado allí. Está solicitando un sistema de permisos basado en la herencia, que puede o no usar atributos, no puede usar enumeraciones, que debe traducirse en atributos que de alguna manera interactúan con MVC y que también se almacenan fácilmente en la base de datos. Tocando muchas capas aquí. – jfar

Respuesta

5

En primer lugar, tengo que darle las gracias por la succión en respuesta a esta;)

Ésta es una respuesta larga, y es sólo un punto de partida. Debe averiguar cómo asignar roles a los usuarios y cómo recrearlos en el AuthenticateRequest.

Si esto no responde su pregunta, espero que sea una inspiración. ¡Disfrutar!

decorar las acciones del controlador

empecé a decorar las dos acciones en el defecto HomeController:

[AuthorizeRoles(Role.Read)] 
    public ActionResult Index() 
    { 
     ViewData["Message"] = "Welcome to ASP.NET MVC!"; 

     return View(); 
    } 

    [AuthorizeRoles(Role.Write)] 
    public ActionResult About() 
    { 
     return View(); 
    } 

Todos los usuarios de la función ReadWrite entonces deben tener acceso. Opté aquí para usar una enumeración como un marcador de posición seguro para las cadenas mágicas. El papel de esta enumeración no es más que ser un marcador de posición. No hay valores enum compuestos, que deben mantenerse en otro lugar. Más sobre eso más tarde.

public enum Role 
{ 
    Read, 
    Write, 
    ReadWrite 
} 

implementar un nuevo atributo de autorización

Dado que las cadenas se han ido, necesito un nuevo atributo autorizar:

public class AuthorizeRolesAttribute : AuthorizeAttribute 
{ 
    private readonly RoleSet authorizedRoles; 

    public AuthorizeRolesAttribute(params Role[] roles) 
    { 
     authorizedRoles = new RoleSet(roles); 
    } 

    protected override bool AuthorizeCore(HttpContextBase httpContext) 
    { 
     return authorizedRoles.Includes(httpContext.User); 
    } 
} 

El RoleSet envuelve un conjunto de valores de enumeración y verifica si un IPrincipal es miembro de uno de ellos:

public class RoleSet 
{ 
    public RoleSet(IEnumerable<Role> roles) 
    { 
     Names = roles.Select(role => role.ToString()); 
    } 

    public bool Includes(IPrincipal user) 
    { 
     return Names.Any(user.IsInRole); 
    } 

    public bool Includes(string role) 
    { 
     return Names.Contains(role); 
    } 

    public IEnumerable<string> Names { get; private set; } 
} 

Mantener papeles

El CompositeRoleSet es donde se registran y se manejan papeles compuestos. CreateDefault() es donde se registran todos los materiales compuestos. Resolve() tomará una lista de roles (valores enum) y convertirá los composites en sus contrapartes individuales.

public class CompositeRoleSet 
{ 
    public static CompositeRoleSet CreateDefault() 
    { 
     var set = new CompositeRoleSet(); 
     set.Register(Role.ReadWrite, Role.Read, Role.Write); 
     return set; 
    } 

    private readonly Dictionary<Role, Role[]> compositeRoles = new Dictionary<Role, Role[]>(); 

    private void Register(Role composite, params Role[] contains) 
    { 
     compositeRoles.Add(composite, contains); 
    } 

    public RoleSet Resolve(params Role[] roles) 
    { 
     return new RoleSet(roles.SelectMany(Resolve)); 
    } 

    private IEnumerable<Role> Resolve(Role role) 
    { 
     Role[] roles; 
     if (compositeRoles.TryGetValue(role, out roles) == false) 
     { 
      roles = new[] {role}; 
     } 

     return roles; 
    } 
} 

Cableado hasta

Necesitamos un usuario autenticado a trabajar. Hice trampa y no modificable una en global.asax:

public MvcApplication() 
    { 
     AuthenticateRequest += OnAuthenticateRequest; 
    } 

    private void OnAuthenticateRequest(object sender, EventArgs eventArgs) 
    { 
     var allRoles = CompositeRoleSet.CreateDefault(); 
     var roles = allRoles.Resolve(Role.ReadWrite); 
     Context.User = new ApplicationUser(roles); 
    } 

Por último, necesitamos una IPrincipal el que entiende todo esto:

public class ApplicationUser : IPrincipal 
{ 
    private readonly RoleSet roles; 

    public ApplicationUser(RoleSet roles) 
    { 
     this.roles = roles; 
    } 

    public bool IsInRole(string role) 
    { 
     return roles.Includes(role); 
    } 

    public IIdentity Identity 
    { 
     get { return new GenericIdentity("User"); } 
    } 
} 
+0

+1 solución muy interesante, debería decir. – gideon

0

parece que quiere algo muy flexible y dependless de lo que puede se exigirá para control de seguridad. Por lo tanto, depende de "hasta dónde estás listo para ir".

Para ayudar a que esta sea una buena dirección, le recomiendo encarecidamente que mire hacia el lado de Control de acceso basado en notificaciones. Y tome this artículo como punto de partida y ejemplo ASP.NET MVC.

Pero recordar que es un complejo tema . Muy flexible (incluso permitiendo Federated Access Control sin ningún cambio de código) pero complejo.

Teníamos que ir de esta manera para que nuestras aplicaciones no estuvieran completamente disponibles para esas implementaciones de "comprobación correcta". Todos nuestros sistemas saben qué "reclamaciones" necesitan para realizar ciertas acciones y las solicitan en función de la identidad de usuario proporcionada (que también es un "reclamo"). Los roles, permisos y otros reclamos se pueden "traducir" fácilmente a los "reclamos" específicos de la aplicación que tienen sentido para nuestras aplicaciones. Completa flexibilidad

P.S. No resuelve los problemas técnicos de "cadenas mágicas" y similares (debe pensar que dependen de su situación) pero le da una arquitectura de control de acceso muy flexible.

0

Así que @thomas parece tener una buena respuesta, pero está envolviendo más su requisito de usar enumeraciones, tomando eso en Roles que IPricipal entenderá.Mi solución es de abajo hacia arriba, así que puedes usar la solución de thomas encima de la mía para implementar IPrincipal

Realmente necesitaba algo similar a lo que quieres y siempre tenía miedo con la autenticación de formularios, (sí, también tienes miedo y Lo sé, pero escúchame) Así que siempre desarrollé mi propia autenticación barata con formularios, pero muchas cosas cambiaron mientras aprendía mvc (en las últimas dos semanas) La autenticación de formularios está muy separada y es muy flexible. Básicamente, no estás realmente usando formularios auth, sino que simplemente estás conectando tu propia lógica al sistema.

Así que aquí es cómo me enfrenté a esto, (tenga cuidado, yo soy un aprendiz yo mismo).

Resumen:

  1. vas para anular algunas de las formas clases de autenticación para autenticar a los usuarios es el propietario, (incluso se puede burlarse de esto)
  2. Estás a continuación, va a crear un IIdentity.
  3. cargar hasta GenericPrincipal con una lista de papeles en cuerdas (lo sé, no hay cuerdas mágicas ... seguir leyendo)

Una vez hecho lo anterior, MVC entiende suficiente para darle lo que quiere! Ahora puede usar [Authorize(Roles = "Write,Read")] sobre cualquier controlador y MVC hará casi todo. Ahora, para las cadenas mágicas, todo lo que tiene que hacer es crear un envoltorio alrededor de ese atributo.


Respuesta larga

utiliza la plantilla de aplicación de Internet que viene con MVC, así que primero se comienza por la creación de proyectos MVC, en el nuevo cuadro de diálogo, supongamos que desea una aplicación de Internet .

Al verificar la aplicación, tendrá una clase principal que anula la autenticación de formularios. IMembershipService Elimine la variable local ProviderProvider __provider_ y en esta clase al menos agregue lógica en el método ValidateUser. (Intente agregar una autenticación falsa a un usuario/pase) También vea la aplicación de prueba v predeterminada creada en VS.

Implementar IIdentity

public class MyIdentity : IIdentity 
    { 
      public MyIdentity(string username) 
      { 
       _username = username;//auth from the DB here. 
       //load up the Roles from db or whatever 
      } 
      string _username; 
      public User UserData { get; set; } 
      #region IIdentity Members 
      public string AuthenticationType 
      { 
       get { return "MyOwn.Authentication"; } 
      } 
      public bool IsAuthenticated 
      { 
       get { return true; } 
      } 
      public string Name 
      { 
       get { return _username; } 
      } 
      #endregion 
      public string[] Roles 
      { 
       get 
       { 
        return //get a list of roles as strings from your Db or something. 
       } 
      } 
    } 

Recuerde que todavía estamos utilizando la plantilla de aplicación de Internet por defecto que viene con un proyecto de MVC.

Así que ahora AccountController.LogOn() debe tener este aspecto:

[HttpPost] 
public virtual ActionResult LogOn(LogOnModel model, string returnUrl) 
{ 
    if (ModelState.IsValid) 
    { 
     if (MembershipService.ValidateUser(model.UserName, model.Password)) 
     { 
      FormsService.SignIn(model.UserName, model.RememberMe); 
      FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(model.UserName, model.RememberMe, 15); 
      string encTicket = FormsAuthentication.Encrypt(ticket); 
      this.Response.Cookies.Add(new HttpCookie(FormsAuthentication.FormsCookieName, encTicket)); 

        if (Url.IsLocalUrl(returnUrl)) 
        { 
         return Redirect(returnUrl); 
        } 
        else 
        { 
         return RedirectToAction("Index", "Home"); 
        } 
       } 
       else 
       { 
        ModelState.AddModelError("", "The user name or password provided is incorrect."); 
       } 
      } 

Así que lo que está haciendo es establecer un boleto formas como una sesión y luego vamos a leer de él en cada petición como esto: Pon esto en Global.asax.cs

public override void Init() 
     { 
     this.PostAuthenticateRequest += new EventHandler(MvcApplication_PostAuthenticateRequest); 
     base.Init(); 
     } 

void MvcApplication_PostAuthenticateRequest(object sender, EventArgs e) 
     { 
      HttpCookie authCookie = HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName]; 
      if (authCookie != null) 
      { 
       string encTicket = authCookie.Value; 
       if (!String.IsNullOrEmpty(encTicket)) 
       { 
         FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(encTicket); 
         MyIdentity id = new MyIdentity(ticket.Name); 
         //HERE is where the magic happens!! 
         GenericPrincipal prin = new GenericPrincipal(id, id.Roles); 
         HttpContext.Current.User = prin; 
       } 
      } 
     } 

he hecho una pregunta uno cómo es eficiente y corregir el método anterior era here.

Ok ahora Huy, se puede decorar sus controladores de la siguiente manera: [Authorize(Roles="RoleA,RoleB")] (más en las cadenas posteriores)

Theres un pequeño problema aquí, si usted adorna su controlador con AuthorizeAttribute, y el usuario conectado no tiene un permiso particular, en lugar de decir "acceso denegado" de forma predeterminada, el usuario será redirigido a la página de inicio de sesión para iniciar sesión de nuevo. Puede solucionar esto así (pellizqué esto desde una respuesta SO):

public class RoleAuthorizeAttribute : AuthorizeAttribute 
    { 
     protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext) 
     { 
      // Returns HTTP 401 
      // If user is not logged in prompt 
      if (!filterContext.HttpContext.User.Identity.IsAuthenticated) 
      { 
       base.HandleUnauthorizedRequest(filterContext); 
      } 
      // Otherwise deny access 
      else 
      { 
       filterContext.Result = new RedirectToRouteResult(@"Default", new RouteValueDictionary{ 
       {"controller","Account"}, 
       {"action","NotAuthorized"} 
       }); 
      } 
     } 
    } 

Ahora todo lo que hacer es añadir otra envoltura alrededor de la AuthorizeAttribute para soportar fuertes tipos que se traducirán en los que las cadenas principales espera. See this article for more.

Planeo actualizar mi aplicación para usar tipos fuertes más tarde, actualizaré esta respuesta a continuación.

Espero que haya sido útil.

Cuestiones relacionadas