¿Cómo integrar "Usuarios" en mi modelo DDD con la autenticación de usuarios?


Estoy creando mi primera ASP.NET MVC sitio y han estado tratando de seguir el desarrollo impulsado por el dominio. Mi sitio es un sitio de colaboración de proyectos donde los usuarios pueden ser asignados 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 tareas. Así que un "Usuario" es un concepto fundamental de mi modelo de dominio.

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

¿Cómo deben relacionarse mis objetos de dominio User e IUserRepository con las funciones más administrativas de mi sitio, como autorizar a los usuarios y permitirles iniciar sesión? ¿Cómo integraría mi modelo de dominio con otros aspectos de ASP.NET como HttpContext.Usuario, HttpContext.De perfil, una costumbre MemberShipProvider, una costumbre ProfileProvider, o personalizado Autorizeattribute?

¿Debo crear un MembershipProvider personalizado y / o un ProfileProvider que envuelva mi IUserRepository? Aunque, también puedo prever por qué puedo querer 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 intentar reinventar la rueda y seguir con el SqlMembershipProvider estándar incorporado en ASP.NET? La información del 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 el estándar ASP.NET cosas de membresía para manejar la creación y autorización de usuarios? Por lo tanto, tendría que haber algún código en algún lugar que sepa crear un perfil para un nuevo usuario en el IUserRepository cuando se crea su cuenta o la primera vez que inician sesión.

Author: Eric Anastas, 2011-07-29

3 answers

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

Nos fuimos con los siguientes enfoques (bien o mal):

Proveedor de Membresía personalizado

Ni yo ni el otro desarrollador somos fans de lo incorporado ASP.NET Proveedor de membresía. Es demasiado hinchado para lo que nuestro sitio se trata (sitio web social simple, impulsado por UGC). Hemos creado 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.

Ticket de Autenticación de Formularios personalizados/Autenticación

Todo en nuestra aplicación utiliza la inyección de dependencias basada en interfaces (StructureMap). Esto incluye la autenticación de formularios. Hemos creado una interfaz muy delgada:

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

Esta sencilla interfaz permite una fácil burla/prueba. Con la implementación, creamos formularios personalizados ticket de autenticación que contiene: cosas como el ID de usuario y los roles, que se requieren en cada solicitud HTTP, no cambian con frecuencia y, por lo tanto, no se deben obtener en cada solicitud.

Luego usamos un filtro de acción para descifrar el ticket de autenticación de formularios (incluidos los roles) y pegarlo en el HttpContext.Current.User.Identity (para el cual nuestro objeto Principal también está basado en la interfaz).

Uso de [Authorize] y [AdminOnly]

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

Simple, tabla única para el usuario, simple POCO

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

Repositorio/Servicio de Usuario

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

Dominio separado de seguridad

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

Cosas como "Login", "Logout" están tratando con authentication y cosas como "User.IsInRole " trata con autorización y por lo tanto no pertenecen en el dominio.

Así que nuestros controladores trabajan tanto con el IAuthenticationService como con el IUserService.

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

Así es como se ve nuestro:

[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 ASP.NET Proveedor de membresía. Es simple y representa nuestro dominio , no ASP.NET es la representación de la misma.

Dicho esto, como dije, tenemos un sitio web simple. Si estás trabajando en un banco sitio web entonces tendría cuidado de reinventar la rueda.

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

Luego calcule sus requisitos de seguridad y elija un proveedor de autenticación (listo para usar o personalizado) apropiadamente.

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

¡Buena suerte!

 14
Author: RPM1984,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-07-29 01:39:33

Permítanme desglosar un poco su colección de preguntas:

Aunque no estoy seguro en este punto si quiero que el userId sea una cadena o un entero.

No tiene que ser un entero por decir, pero definitivamente use algún tipo de valor basado en bits aquí (por ejemplo, int, long o guid). Un índice que opera sobre un valor de tamaño fijo es mucho más rápido que un índice sobre una cadena, y en su vida, nunca se quedará sin identificadores para su usuario.

¿Cómo deben relacionarse mis objetos de dominio User e IUserRepository con las funciones más administrativas de mi sitio, como autorizar a los usuarios y permitirles iniciar sesión?

Decida si desea utilizar el asp.net membresía o no. Recomiendo no por la razón de que en su mayoría solo se hincha y tienes que implementar la mayoría de las características de ti mismo de todos modos, como la verificación de correo electrónico, que pensarías al mirar las tablas generado sería construido en... El proyecto de plantilla para ASP.NET MVC 1 y 2 ambos incluyen un repositorio de membresía simple, solo reescribe las funciones que realmente validan al usuario y estarás bien en tu camino.

¿Cómo integraría mi modelo de dominio con otros aspectos de ASP.NET como HttpContext.Usuario, HttpContext.¿Perfil, un proveedor de membresía personalizado, un proveedor de perfil personalizado o un atributo de autorización personalizado?

Cada uno de ellos es digno de su propia pregunta, y cada uno ha sido preguntado aquí antes. Dicho esto, HttpContext.User solo es útil si está utilizando la funcionalidad incorporada FormsAuthentication y le recomiendo usarla al principio hasta que encuentre una situación en la que no haga lo que desea. 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 una solicitud al comienzo de cada solicitud si HttpContext.User.IsAuthenticated es true.

En cuanto al perfil, evito peticiones stateful con una pasión, y nunca lo han utilizado antes, por lo que alguien más tendrá que ayudarle con que uno.

Todo lo que necesita para usar el atributo construido en [Authorize] es decirle a FormsAuthentication que el usuario está valdiado. Si desea utilizar la característica roles del atributo authorize, escriba su propio RoleProvider y funcionará como magia. Puedes encontrar muchos ejemplos para eso en Stack Overflow. HACK: Solo tienes que implementar RoleProvider.GetAllRoles(), RoleProvider.GetRolesForUser(string username), y RoleProvider.IsUserInRole(string username, string roleName) para que funcione. Vosotros no tenéis para implementar toda la interfaz a menos que desee utilizar toda la funcionalidad de la asp.net sistema de membresía.

Sería mejor no intentar 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 hacer 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;
    }
}
 4
Author: Nick Larsen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-10-19 18:09:29

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

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

En mi opinión, debería tener esta implementación como parte de la infraestructura (Simplemente cree un nuevo proyecto de seguridad o como quieras llamarlo) e implementar este ejemplo de arriba. A continuación, llame a este mecanismo desde la capa de aplicación. Recuerde que la capa de aplicación controla y organiza toda la operación en su aplicación. La capa de dominio debe preocuparse exclusivamente por las operaciones comerciales, no por el acceso o la persistencia de datos, etc.. Es ignorante sobre cómo autenticas a las personas en tu sistema.

Piensa en una empresa de ladrillo y mortero. El sistema de acceso de huellas dactilares implementado tiene nada que ver con las operaciones de esta compañía, pero aún así, es parte de la infraestructura (edificio). De hecho, controla quién tiene acceso a la empresa, para que puedan hacer sus respectivas tareas. No tienes dos empleados, uno para escanear su huella digital para que el otro pueda entrar y hacer su trabajo. Sólo tienes un empleado con un dedo índice. Para "acceder" todo lo que necesitas es su dedo... Por lo tanto, su repositorio, si va a utilizar el mismo UserRepository para la autenticación, debe contiene un método para la autenticación. Si ha decidido utilizar un AccessService en su lugar (este es un servicio de aplicación, no uno de dominio), debe incluir UserRepository para acceder a los datos de ese usuario, obtener su información de dedo (nombre de usuario y contraseña) y compararlo con lo que viene del formulario (escaneo de dedo). ¿Me expliqué bien?

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

 1
Author: Pepito Fernandez,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-11-04 20:59:07