How do integrate “Users” in my DDD model with authenticating users?

后端 未结 3 1809
伪装坚强ぢ
伪装坚强ぢ 2021-01-30 16:34

I\'m creating my first ASP.NET MVC site and have been trying to follow domain driven development. My site is a project collaboration site where users can be assigned to one or m

3条回答
  •  既然无缘
    2021-01-30 17:16

    Yes - very good question. Like @Andrew Cooper, our team also went through all this.

    We went with the following approaches (right or wrong):

    Custom Membership Provider

    Neither I or the other developer are fans of the built in ASP.NET Membership provider. It's way too bloated for what our site is about (simple, UGC-driven social website). We created a very simple one that does what our application needs, and nothing more. Whereas the built-in membership provider does everything you might need, but most likely won't.

    Custom Forms Authentication Ticket/Authentication

    Everything in our application uses interface-driven dependency injection (StructureMap). This includes Forms Authentication. We created a very thin interface:

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

    This simple interface allows easy mocking/testing. With the implementation, we create a custom forms authentication ticket containing: things like the UserId and the Roles, which are required on every HTTP request, do not frequently change and therefore should not be fetched on every request.

    We then use an action filter to decrypt the forms authentication ticket (including the roles) and stick it in the HttpContext.Current.User.Identity (for which our Principal object is also interface-based).

    Use of [Authorize] and [AdminOnly]

    We can still make use of the authorization attributes in MVC. And we also created one for each role. [AdminOnly] simply checks the role for the current user, and throws a 401 (forbidden).

    Simple, single table for User, simple POCO

    All user information is stored in a single table (with the exception of "optional" user info, such as profile interests). This is mapped to a simple POCO (Entity Framework), which also has domain-logic built into the object.

    User Repository/Service

    Simple User Repository that is domain-specific. Things like changing password, updating profile, retrieving users, etc. The repository calls into domain logic on the User object i mentioned above. The service is a thin wrapper on top of the repository, which seperates single repository methods (e.g Find) into more specialized ones (FindById, FindByNickname).

    Domain seperated from security

    Our "domain" the User and his/her's association information. This includes name, profile, facebook/social integration, etc.

    Things like "Login", "Logout" are dealing with authentication and things like "User.IsInRole" deals with authorization and therefore do not belong in the domain.

    So our controllers work with both the IAuthenticationService and the IUserService.

    Creating a profile is a perfect example of domain logic, that is mixed with authentication logic also.

    Here's what our's looks like:

    [HttpPost]
    [ActionName("Signup")]
    public ActionResult Signup(SignupViewModel model)
    {
        if (ModelState.IsValid)
        {
            try
            {
                // Map to Domain Model.
                var user = Mapper.Map(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);
    }
    

    Summary

    The above has worked well for us. I love having a simple User table, and not that bloated madness that is the ASP.NET Membership provider. It's simple and represents our domain, not ASP.NET's representation of it.

    That being said, as i said we have a simple website. If you're working on a banking website then i would be careful about re-inventing the wheel.

    My advice to use is create your domain/model first, before you even think about authentication. (of course, this is what DDD is all about).

    Then work out your security requirements and choose an authentication provider (off the shelf, or custom) appropriately.

    Do not let ASP.NET dictate how your domain should be designed. This is the trap most people fall into (including me, on a previous project).

    Good luck!

提交回复
热议问题