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

后端 未结 3 1804
伪装坚强ぢ
伪装坚强ぢ 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<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);
    }
    

    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!

    0 讨论(0)
  • 2021-01-30 17:20

    Let me break down your collection of questions a bit:

    Although I'm not sure at this point if I want the UserId to be a string or integer.

    It doesn't have to be an integer per say, but definitely use some kind of bit based value here (e.g. int, long or guid). An index operating over a fixed size value is much faster than an index over a string, and in your life time, you will never run out of identifiers for your users.

    How should my domain objects User and IUserRepository relate to the more administrative functions of my site like authorizing users and allowing them to login?

    Decide if you want to use the built in asp.net membership or not. I recommend not for the reason that it's mostly just bloat and you have to implement most of the features of it yourself anyway, like email verification, which you'd think from looking at the tables generated it would be built in... The template project for ASP.NET MVC 1 and 2 both include a simple membership repository, just rewrite the functions that actually validate the user and you'll be well on your way.

    How would I integrate my domain model with other aspects of ASP.NET such as HttpContext.User, HttpContext.Profile, a custom MemberShipProvider, a custom ProfileProvider, or custom AuthorizeAttribute?

    Each one of these is worthy of it's own SO question, and each has been asked here before. That being said, HttpContext.User is only useful if you are using the built in FormsAuthentication functionality and I recommend using it in the beginning until you encounter a situation where it is does not do what you want. I like storing the user key in the name when signing in with FormsAuthentication and loading a request bound current user object at the beginning of every request if HttpContext.User.IsAuthenticated is true.

    As for the profile, I avoid stateful requests with a passion, and have never used it before, so someone else will have to help you with that one.

    All you need to use the built in [Authorize] attribute is to tell FormsAuthentication the user is valdiated. If you want to use the roles feature of the authorize attribute, write your own RoleProvider and it will work like magic. You can find plenty of examples for that on Stack Overflow. HACK: You only have to implement RoleProvider.GetAllRoles(), RoleProvider.GetRolesForUser(string username), and RoleProvider.IsUserInRole(string username, string roleName) in order to have it work. You do not have to implement the entire interface unless you wish to use all of the functionality of the asp.net membership system.

    Would it be better to not try and reinvent the wheel and stick with the standard SqlMembershipProvider built into ASP.NET?

    The pragmatic answer for every derivation of this question is to not reinvent the wheel until the wheel doesn't do what you need it to do, how you need it to do it.

    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;
        }
    }
    
    0 讨论(0)
  • 2021-01-30 17:33

    I know my answer comes a little bit late, but for future references to other colleagues having the same question.

    Here is an example of Custom Authentication and Authorization using Roles as well. http://www.codeproject.com/Articles/408306/Understanding-and-Implementing-ASP-NET-Custom-Form. It's a very good article, very fresh and recent.

    In my opinion, you should have this implementation as part of the infrastructure (Just create a new project Security or whatever you want to call it) and implement this example above there. Then call this mechanism from your Application Layer. Remember that the Application layer controls and orchestrate the whole operation in your application. Domain layer should be concern exclusively about business operations, not about access or data persistence, etc.. It's ignorant on how you authenticate people in your system.

    Think of a brick and mortar company. The fingerprint access system implemented has nothing to do with this company's operations, but still, it's part of the infrastructure (building). As a matter of fact, it controls who have access to the company, so they can do their respective duties. You don't have two employees, one to scan his fingerprint so the other can walk in and do its job. You have only an Employee with an index finger. For "access" all you need is his finger... So, your repository, if you are going to use the same UserRepository for authentication, should contain a method for authentication. If you decided to use an AccessService instead (this is an application service, not a domain one), you need to include UserRepository so you access that user data, get his finger information (username and password) and compares it with whatever is coming from the form (finger scan). Did I explain myself right?

    Most of DDD's situations apply to real life's situations... when it comes to architecture of the software.

    0 讨论(0)
提交回复
热议问题