2011-07-28 26 views
37

Estoy creando mi primer sitio ASP.NET MVC y he estado tratando de seguir el desarrollo impulsado por el dominio. Mi sitio es un sitio de colaboración de proyecto donde los usuarios pueden asignarse a uno o más proyectos en el sitio. Las tareas se agregan a los proyectos y los usuarios con un proyecto se pueden asignar a las tareas. Entonces, un "Usuario" es un concepto fundamental de mi modelo de dominio.¿Cómo se integran los "Usuarios" en mi modelo DDD con la autenticación de usuarios?

Mi plan es tener un objeto de modelo "Usuario" que contenga toda la información sobre un usuario y se pueda acceder a través de un IUserServeritorio. Cada usuario puede ser identificado por un UserId. Aunque no estoy seguro en este momento si quiero que UserId sea una cadena o un entero.

¿Cómo se relacionan los objetos de mi dominio User y IUserRepository con las funciones más administrativas de mi sitio, como autorizar a los usuarios y permitirles iniciar sesión? ¿Cómo podría integrar mi modelo de dominio con otros aspectos de ASP.NET como HttpContext.User, HttpContext.Profile, MemberShipProvider personalizado, ProfileProvider personalizado o AuthorizeAttribute personalizado?

¿Debo crear un MembershipProvider y/o ProfileProvider personalizado que envuelva mi IUserRepository? Aunque también puedo prever por qué deseo separar la información del Usuario en mi modelo de dominio de la autorización de un usuario en mi sitio. Por ejemplo, en el futuro, es posible que desee cambiar a la autenticación de Windows desde la autenticación de formularios.

¿Sería mejor no tratar de reinventar la rueda y seguir con el estándar SqlMembershipProvider integrado en ASP.NET? La información de perfil de cada usuario se almacenaría en el modelo de dominio (User/IUserRepository), pero esto no incluiría su contraseña. Entonces, ¿usaría la membresía estándar de ASP.NET para manejar la creación y autorización de usuarios? Por lo tanto, debería haber algún código en algún lugar que sepa crear un perfil para un nuevo usuario en IUserRepository cuando se crea su cuenta o la primera vez que inicia sesión.

+1

Buena pregunta, y estoy ansioso por ver respuestas decentes. Abordé el mismo tema en un proyecto hace aproximadamente 4 meses, y no estoy seguro de que esté contento con el enfoque que terminé tomando. –

+1

Hubiera votado esta pregunta, pero realmente es como 10 preguntas en una. El mejor método para hacer preguntas específicas con respuesta es simplemente comenzar a crear prototipos, y decidir si vale la pena el esfuerzo de aprender cada uno de estos sistemas en lugar de escribirlos usted mismo. Ninguno es muy difícil, y probablemente debas dejar que tus objetivos guíen tus decisiones. –

+0

relacionado: http://stackoverflow.com/questions/3964989/how-to-pass-current-user-information-to-all-layers-in-ddd/3969014#3969014 –

Respuesta

14

Sí - muy buena pregunta. Al igual que @Andrew Cooper, nuestro equipo también pasó por todo esto.

Fuimos con los siguientes enfoques (correcto o incorrecto):

suscripciones personalizado Proveedor

Ni yo u otro desarrollador son fans de la incorporada en el proveedor de pertenencia ASP.NET . Está demasiado saturado para lo que trata nuestro sitio (simple, sitio web social impulsado por UGC). Creamos uno muy simple que hace lo que nuestra aplicación necesita, y nada más. Mientras que el proveedor de membresía incorporado hace todo lo que podría necesitar, pero lo más probable es que no lo haga.

formularios personalizados de autenticación de entradas/Autenticación

Todo en nuestra aplicación utiliza la inyección de dependencias interfaz impulsada (StructureMap). Esto incluye la Autenticación de formularios. Creamos una interfaz muy delgada:

public interface IAuthenticationService 
{ 
    void SignIn(User user, HttpResponseBase httpResponseBase); 
    void SignOut(); 
} 

Esta interfaz simple permite simulaciones/pruebas fáciles. Con la implementación, creamos un ticket de autenticación de formularios personalizado que contiene: elementos como UserId y Roles, que se requieren en cada solicitud de HTTP, no cambian con frecuencia y, por lo tanto, no se deben buscar en cada solicitud.

A continuación, utilizamos un filtro de acción para descifrar el ticket de autenticación de formularios (incluidas las funciones) y lo pegamos en el HttpContext.Current.User.Identity (para el cual nuestro objeto Principal también está basado en interfaz).

El uso de [Autorizar] y [AdminOnly]

Todavía pueden hacer uso de la autorización en los atributos MVC. Y también creamos uno para cada función. [AdminOnly] simplemente comprueba el rol para el usuario actual y arroja un 401 (prohibido).

simple, mesa única para el usuario, sencilla POCO

Toda la información de usuario se almacena en una sola tabla (con la excepción de la información "opcional" del usuario, tales como los intereses del perfil). Esto se asigna a un POCO (Entity Framework) simple, que también tiene una lógica de dominio incorporada en el objeto.

Repositorio de usuario/servicio

simple repositorio de usuario que es dominio específico. Cosas como cambiar la contraseña, actualizar el perfil, recuperar usuarios, etc. El repositorio llama a la lógica de dominio en el objeto Usuario que mencioné anteriormente. El servicio es una envoltura delgada en la parte superior del repositorio, que separa los métodos de repositorio individuales (por ejemplo, Buscar) en otros más especializados (FindById, FindByNickname).

dominio separado de la seguridad

Nuestro "dominio" del usuario y su/información de su asociación. Esto incluye el nombre, perfil, facebook/integración social, etc.

Cosas como "Login", "Salir" se trata de la autenticación y cosas como "ofertas User.IsInRole" con la autorización y por lo tanto no pertenecen en el dominio.

Así que nuestros controladores funcionan tanto con IAuthenticationService como con IUserService.

La creación de un perfil es un ejemplo perfecto de lógica de dominio, que también se combina con la lógica de autenticación.

Esto es lo que nuestros de las miradas como:

[HttpPost] 
[ActionName("Signup")] 
public ActionResult Signup(SignupViewModel model) 
{ 
    if (ModelState.IsValid) 
    { 
     try 
     { 
      // Map to Domain Model. 
      var user = Mapper.Map<SignupViewModel, Core.Entities.Users.User>(model); 

      // Create salt and hash password.   
      user.Password = _authenticationService.SaltAndHashPassword(); 

      // Signup User. 
      _userService.Save(user); 

      // Save Changes. 
      _unitOfWork.Commit(); 

      // Forms Authenticate this user. 
      _authenticationService.SignIn(user, Response); 

      // Redirect to homepage. 
      return RedirectToAction("Index", "Home", new { area = "" }); 
     } 
     catch (Exception exception) 
     { 
      ModelState.AddModelError("SignupError", "Sorry, an error occured during Signup. Please try again later."); 
      _loggingService.Error(exception); 
     } 
    } 

    return View(model); 
} 

Resumen

Lo anterior ha funcionado bien para nosotros. I me encanta tener una tabla de usuario simple, y no esa locura hinchada que es el proveedor de membresía de ASP.NET. Es simple y representa nuestro dominio , no la representación de ASP.NET de él.

Dicho esto, como he dicho, tenemos un sitio web simple. Si está trabajando en un sitio web bancario, tendría cuidado de reinventar la rueda.

Mi consejo para usar es crear su dominio/modelo primero, antes de siquiera pensar en la autenticación. (por supuesto, esto es de lo que se trata DDD).

Luego, determine sus requisitos de seguridad y elija un proveedor de autenticación (comercial o personalizado) de forma adecuada.

No deje que ASP.NET dicte cómo debe diseñarse su dominio. Esta es la trampa en la que la mayoría de la gente cae (incluido yo, en un proyecto anterior).

¡Buena suerte!

+0

¡Gracias! Esta respuesta realmente ayuda mucho.Todavía estoy tratando de armar todo en mi cabeza. Cuando habló sobre el uso de un "Proveedor de membresía personalizado", ¿quiere decir que realmente tiene una clase personalizada que hereda de System.Web.Security.MembershipProvider y se identifica en un elemento de membresía en su archivo web.config? ¿Podría también publicar algún código de muestra para los métodos de entrada y salida de sesión POST? –

+0

@Eric: no, no es un proveedor de membresía personalizado. Es una clase personalizada que satisface los requisitos. ASP.NET no lo sabe. Solo se preocupa por HttpContext.Request.IsAuthenticated. No estamos utilizando la API del proveedor de membresía. Los métodos de acción de inicio de sesión/cierre de sesión son triviales (formas auth signin, signout, etc.) – RPM1984

+0

¿Cómo se crea el ticket de autenticación? ¿Está utilizando la autenticación regular de formularios de ASP.NET para crear el ticket de autenticación? En otras palabras, ¿su implementación de IAuthorizationService llama a FormsAuthentication.SetAuthCookie() y FormsAuthentication.SignOut()? O agrega una cookie manualmente llamando a HttpResponseBase.AppendCookie(). Estoy confundido por qué su IAuthenticationService necesita pasar un HttpResponseBase? –

4

Vamos a dividirla abajo de su colección de preguntas un poco:

Aunque no estoy seguro en este momento si quiero que la identificación de usuario sea una cadena o entero.

No tiene que ser un número entero por decir, pero definitivamente el uso de algún tipo de valor basado poco aquí (por ejemplo, int, long o GUID). Un índice que opere sobre un valor de tamaño fijo es mucho más rápido que un índice sobre una cadena, y en su tiempo de vida, nunca se quedará sin identificadores para sus usuarios.

¿Cómo debe mi dominio objetos de usuario y IUserRepository se refieren a las funciones más administrativas de mi sitio como autorizar a los usuarios y que les permite acceso?

decidir si desea utilizar el construido en asp.net pertenencia o no. Recomiendo no por la razón de que en su mayor parte es simplemente abotargado y usted tiene que implementar la mayoría de las características de usted mismo de todos modos, como la verificación de correo electrónico, lo que se podría pensar al mirar las tablas generadas en las que se generaría ... La plantilla El proyecto para ASP.NET MVC 1 y 2 incluye un repositorio de membresía simple, solo reescribe las funciones que realmente validan al usuario y estarás en camino.

¿Cómo integrar mi modelo de dominio con otros aspectos de ASP.NET como HttpContext.User, HttpContext.Profile, un MembershipProvider costumbre, una costumbre ProfileProvider, o la costumbre AuthorizeAttribute?

Cada uno de ellos es digno de su propio SO pregunta, y cada uno se ha pedido aquí antes. Dicho esto, HttpContext.User solo es útil si está utilizando la funcionalidad incorporada en FormsAuthentication y le recomiendo usarla al principio hasta que encuentre una situación en la que no haga lo que desee. Me gusta almacenar la clave de usuario en el nombre al iniciar sesión con FormsAuthentication y cargar un objeto de usuario actual vinculado a la solicitud al comienzo de cada solicitud si HttpContext.User.IsAuthenticated es true.

En cuanto al perfil, evito las solicitudes de estado con pasión, y nunca lo he usado antes, por lo que alguien más tendrá que ayudarlo con eso.

Todo lo que necesita para usar el atributo [Authorize] incorporado es decirle FormsAuthentication que el usuario valdiated. Si desea utilizar la función de roles del atributo authorize, escriba su propio RoleProvider y funcionará como magia. Puede encontrar muchos ejemplos para eso en Stack Overflow. HACK: solo tiene que implementar RoleProvider.GetAllRoles(), RoleProvider.GetRolesForUser(string username) y RoleProvider.IsUserInRole(string username, string roleName) para que funcione. No tiene que implementar toda la interfaz a menos que desee utilizar toda la funcionalidad del sistema de membresía asp.net.

¿Sería mejor no tratar de reinventar la rueda y seguir con el SqlMembershipProvider estándar incorporado en ASP.NET?

La respuesta pragmática para cada derivación de esta pregunta es no reinventar la rueda hasta que la rueda no hace lo que necesita hacer, cómo tiene que hacerlo.

if (built in stuff works fine) { 
    use the built in stuff; 
} else { 
    write your own; 
} 

if (easier to write your own then figure out how to use another tool) { 
    write your own; 
} else { 
    use another tool; 
} 

if (need feature not in the system) { 
    if (time to extend existing api < time to do it yourself) { 
     extend api; 
    } else { 
     do it yourself; 
    } 
} 
+1

¿cuánto tiempo tarda tu cerebro en ejecutar el bloque de código inferior? – CRice

+1

@CRice: menos tiempo en cada iteración. –

+1

Ese bloque de código es kick ass^_^ –

1

Sé que mi respuesta llega un poco tarde, pero para futuras referencias a otros colegas con la misma pregunta.

Aquí hay un ejemplo de Autenticación y Autorización Personalizadas utilizando Roles también. http://www.codeproject.com/Articles/408306/Understanding-and-Implementing-ASP-NET-Custom-Form. Es un artículo muy bueno, muy reciente y reciente.

En mi opinión, debe tener esta implementación como parte de la infraestructura (simplemente cree un nuevo proyecto de Seguridad o como quiera llamarlo) e implemente este ejemplo de arriba. Luego llame a este mecanismo desde su capa de aplicación. Recuerde que la capa de Aplicación controla y orquesta toda la operación en su aplicación. La capa de dominio debe preocuparse exclusivamente de las operaciones comerciales, no del acceso o la persistencia de los datos, etc. Es ignorante sobre cómo autentica personas en su sistema.

Piense en una empresa de ladrillo y mortero. El sistema de acceso de huellas dactilares implementado no tiene nada que ver con las operaciones de esta empresa, pero aún así, es parte de la infraestructura (construcción). De hecho, controla quién tiene acceso a la empresa para que pueda cumplir con sus deberes respectivos. No tiene dos empleados, uno para escanear su huella digital para que el otro pueda entrar y hacer su trabajo. Solo tiene un empleado con un dedo índice. Para "acceder", todo lo que necesita es su dedo ... Por lo tanto, su repositorio, si va a utilizar el mismo UserRepository para la autenticación, debe contener un método para la autenticación. Si decidió usar un AccessService (este es un servicio de aplicación, no uno de dominio), necesita incluir UserRepository para acceder a los datos de usuario, obtener su información de usuario (nombre de usuario y contraseña) y compararla con lo que venga de la forma (escaneo de dedos). ¿Me expliqué, verdad?

La mayoría de las situaciones de DDD se aplican a situaciones de la vida real ... cuando se trata de la arquitectura del software.

Cuestiones relacionadas