Comment intégrer les "utilisateurs" dans mon modèle DDD avec l'authentification des utilisateurs?

Je crée mon premier ASP.NET MVC site et ont essayé de suivre le développement axé sur le domaine. Mon site est un site de collaboration de projet où les utilisateurs peuvent être affectés à un ou plusieurs projets sur le site. Les tâches sont ensuite ajoutées aux projets, et les utilisateurs avec dans un projet peuvent être affectés à des tâches. Ainsi, un "Utilisateur" est un concept fondamental de mon modèle de domaine.

Mon plan est d'avoir un objet modèle "User" qui contient toutes les informations sur un utilisateur et peut être consulté via un IUserRepository. Chaque utilisateur peut être identifié par un Identifiant. Bien que je ne sois pas sûr à ce stade si je veux que L'ID utilisateur soit une chaîne ou un entier.

Comment mes objets de domaine User et IUserRepository devraient-ils se rapporter aux fonctions plus administratives de mon site comme autoriser les utilisateurs et leur permettre de se connecter? Comment puis-je intégrer mon modèle de domaine avec d'autres aspects de ASP.NET tels que HttpContext.Utilisateur, HttpContext.Profil, un MemberShipProvider personnalisé, un ProfileProvider personnalisé ou AuthorizeAttribute?

Dois-je créer un MembershipProvider personnalisé et ou ProfileProvider qui enveloppe mon IUserRepository? Bien que, je peux également prévoir pourquoi je pourrais vouloir séparer les informations D'utilisateur dans mon modèle de domaine de l'autorisation d'un utilisateur sur mon site. Par exemple, à l'avenir, je pourrais passer à l'authentification windows à partir de l'authentification des formulaires.

Serait-il préférable de ne pas essayer de réinventer la roue et de s'en tenir au SqlMembershipProvider standard intégré ASP.NET? les informations de profil de chaque utilisateur seraient stockées dans le modèle de domaine (User / IUserRepository), mais cela n'inclurait pas leur mot de passe. J'utiliserais alors la norme ASP.NET trucs d'adhésion pour gérer la création et l'autorisation des utilisateurs? Il devrait donc y avoir du code quelque part pour créer un profil pour un nouvel utilisateur dans IUserRepository lorsque leur compte est créé ou la première fois qu'ils se connectent.

38
demandé sur Eric Anastas 2011-07-29 03:59:32

3 réponses

Oui - très bonne question. Comme @ Andrew Cooper, notre équipe a également traversé tout cela.

Nous sommes allés avec les approches suivantes (bonnes ou mauvaises):

Fournisseur D'Appartenances Personnalisé

Ni moi ni l'autre développeur ne sommes fans du built in ASP.NET Fournisseur D'adhésion. Il est beaucoup trop gonflé pour ce que notre site est sur le point (simple, site social axé sur UGC). Nous avons créé un très simple qui fait ce dont notre application a besoin, et rien de plus. Alors que le fournisseur d'adhésion intégré fait tout ce dont vous pourriez avoir besoin, mais très probablement pas.

Formulaires Personnalisés Ticket D'Authentification/Authentification

Tout dans notre application utilise l'injection de dépendance pilotée par l'interface (StructureMap). Cela inclut L'authentification des formulaires. Nous avons créé une interface très fine:

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

Cette interface simple permet des moqueries/tests faciles. Avec la mise en œuvre, nous créons un formulaire personnalisé ticket d'authentification contenant: des choses comme L'ID utilisateur et les rôles, qui sont requis sur chaque requête HTTP, ne changent pas fréquemment et ne doivent donc pas être récupérées sur chaque requête.

Nous utilisons ensuite un filtre d'action pour déchiffrer le ticket d'authentification des formulaires (y compris les rôles) et le coller dans le HttpContext.Current.User.Identity (pour lequel notre objet Principal est également basé sur l'interface).

L'Utilisation de [Autoriser] et [AdminOnly]

Nous pouvons encore utiliser le attributs d'autorisation dans MVC. Et nous avons également créé un pour chaque rôle. [AdminOnly] vérifie simplement le rôle de l'utilisateur actuel et lance un 401 (interdit).

Simple, table unique pour L'utilisateur, simple POCO

Toutes les informations utilisateur sont stockées dans une seule table (à l'exception des informations utilisateur "facultatives", telles que les intérêts du profil). Ceci est mappé à un simple poco (Entity Framework), qui a également une logique de domaine intégrée dans l'objet.

Référentiel Utilisateur / Service

Référentiel utilisateur Simple spécifique au domaine . Des choses comme changer le mot de passe, mettre à jour le profil, récupérer les utilisateurs, etc. Le référentiel appelle la logique de domaine sur l'objet utilisateur que j'ai mentionné ci-dessus. Le service est un wrapper mince au-dessus du référentiel, qui sépare les méthodes de référentiel unique (par exemple Find) en méthodes plus spécialisées (FindById, FindByNickname).

Domaine séparé de de sécurité

Notre "domaine" l'Utilisateur et ses informations d'association. Cela inclut le nom, le profil, l'intégration facebook/sociale, etc.

Des choses comme "connexion", "déconnexion" traitent de authentification et des choses comme " Utilisateur.IsInRole " traite de l'autorisation {[18] } et n'appartient donc pas au domaine.

Donc, nos contrôleurs fonctionnent à la fois avec le IAuthenticationService et le IUserService.

Créer un profil est un parfait exemple de logique de domaine, qui est mélangé avec la logique d'authentification aussi.

Voici à quoi ressemble notre:

[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);
}

Résumé

Ce qui précède a bien fonctionné pour nous. Je aime avoir une table utilisateur simple, et pas cette folie gonflée qui est la ASP.NET Fournisseur D'adhésion. C'est simple et représente notre domaine , Pas ASP.NET la représentation de celui-ci.

Cela étant dit, comme je l'ai dit, nous avons un site web simple. Si vous travaillez sur une banque site web alors je ferais attention à réinventer la roue.

Mon conseil à utiliser est de créer d'abord votre domaine/modèle, avant même de penser à l'authentification. (bien sûr, C'est ce que DDD est tout au sujet).

ensuite, déterminez vos exigences de sécurité et choisissez un fournisseur d'authentification (standard ou personnalisé) de manière appropriée.

Ne laissez pas ASP.NET dictez comment votre domaine doit être conçu. C'est le piège dans lequel la plupart des gens tombent (y compris moi, sur un projet précédent).

Bonne chance!

14
répondu RPM1984 2011-07-29 01:39:33

Laissez-moi décomposer votre collection de questions un peu:

Bien que je ne sois pas sûr à ce stade si je veux que L'ID utilisateur soit une chaîne ou un entier.

Il ne doit pas nécessairement être un entier par exemple, mais certainement utiliser une sorte de valeur basée sur les bits ici (par exemple int, long ou guid). Un index fonctionnant sur une valeur de taille fixe est beaucoup plus rapide qu'un index sur une chaîne, et dans votre vie, vous ne manquerez jamais d'identifiants pour votre utilisateur.

Comment mes objets de domaine User et IUserRepository devraient-ils se rapporter aux fonctions plus administratives de mon site comme autoriser les utilisateurs et leur permettre de se connecter?

Décidez si vous voulez utiliser le built in asp.net adhésion ou non. Je ne recommande pas pour la raison que c'est surtout juste un ballonnement et que vous devez implémenter la plupart des fonctionnalités de celui-ci vous-même de toute façon, comme la vérification par e-mail, que vous penseriez en regardant les tables il a généré serait construit dans... Le projet modèle pour ASP.NET MVC 1 et 2 comprennent tous deux un simple référentiel d'adhésion, il suffit de réécrire les fonctions qui valident réellement l'utilisateur et vous serez bien sur votre chemin.

Comment puis-je intégrer mon modèle de domaine avec d'autres aspects de ASP.NET tels que HttpContext.Utilisateur, HttpContext.Profil, un MemberShipProvider personnalisé, un ProfileProvider personnalisé ou un AuthorizeAttribute personnalisé?

Chacun d'eux est digne de sa propre question SO, et chacun a été demandé ici avant. Cela étant dit, HttpContext.L'utilisateur n'est utile que si vous utilisez la fonctionnalité FormsAuthentication intégrée et je recommande de l'utiliser au début jusqu'à ce que vous rencontriez une situation où il ne fait pas ce que vous voulez. J'aime stocker la clé utilisateur dans le nom lors de la connexion avec FormsAuthentication et charger un objet utilisateur actuel lié à la requête au début de chaque requête si HttpContext.User.IsAuthenticated est true.

Quant au profil, j'évite demandes stateful avec une passion, et ne l'ont jamais utilisé auparavant, alors quelqu'un d'autre devra vous aider avec celui-ci.

Tout ce dont vous avez besoin pour utiliser l'attribut [Authorize] intégré est de dire à FormsAuthentication que l'utilisateur est valdiated. Si vous souhaitez utiliser la fonction roles de l'attribut authorize, écrivez votre propre RoleProvider et cela fonctionnera comme par magie. Vous pouvez trouver beaucoup d'exemples pour cela sur Stack Overflow. HACK: il vous suffit de mettre en œuvre RoleProvider.GetAllRoles(), RoleProvider.GetRolesForUser(string username), et RoleProvider.IsUserInRole(string username, string roleName) pour que ça marche. Vous n'avez pas pour implémenter l'interface entière sauf si vous souhaitez utiliser toutes les fonctionnalités de la asp.net système d'adhésion.

Serait-il préférable de ne pas essayer de réinventer la roue et de s'en tenir au SqlMembershipProvider standard intégré ASP.NET?

La réponse pragmatique à chaque dérivation de cette question Est de ne pas réinventer la roue tant que la roue ne fait pas ce que vous avez besoin de faire, comment vous en avez besoin pour le faire.

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
répondu Nick Larsen 2011-10-19 18:09:29

Je sais que ma réponse arrive un peu tard, mais pour de futures références à d'autres collègues ayant la même question.

Voici un exemple D'authentification et D'autorisation personnalisées utilisant également des rôles. http://www.codeproject.com/Articles/408306/Understanding-and-Implementing-ASP-NET-Custom-Form. c'est un très bon article, très frais et récent.

À mon avis, vous devriez avoir cette implémentation dans le cadre de l'infrastructure (il suffit de créer une nouvelle sécurité de projet ou tout ce que vous voulez l'appeler) et implémentez cet exemple ci-dessus. Appelez ensuite ce mécanisme à partir de votre couche D'Application. Rappelez - vous que la couche D'Application contrôle et orchestre l'ensemble de l'opération dans votre application. La couche de domaine devrait concerner exclusivement les opérations commerciales, pas l'accès ou la persistance des données,etc.. Il est ignorant sur la façon dont vous authentifiez les gens dans votre système.

Pensez à une entreprise de briques et de mortier. Le système d'accès aux empreintes digitales mis en œuvre a rien à voir avec les opérations de cette entreprise, mais quand même, cela fait partie de l'infrastructure (bâtiment). En fait, il contrôle qui ont accès à l'entreprise, afin qu'ils puissent faire leurs tâches respectives. Vous n'avez pas deux employés, l'un pour scanner ses empreintes digitales afin que l'autre puisse entrer et faire son travail. Vous avez seulement un employé avec un index. Pour "accès" Tout ce dont vous avez besoin est son doigt... Donc, votre référentiel, si vous allez utiliser le même UserRepository pour l'authentification, devrait contenir une méthode pour l'authentification. Si vous avez décidé d'utiliser un AccessService à la place (c'est un service d'application, pas un domaine), vous devez inclure UserRepository pour accéder à ces données utilisateur, obtenir ses informations de doigt (nom d'utilisateur et mot de passe) et les comparer avec tout ce qui vient du formulaire (finger scan). Je n'ai moi-même expliquer à droite?

La plupart des situations de DDD s'appliquent aux situations de la vie réelle... quand il s'agit de l'architecture du logiciel.

1
répondu Pepito Fernandez 2012-11-04 20:59:07