如何将我的 DDD 模型中的“用户”与身份验证用户集成?

Posted

技术标签:

【中文标题】如何将我的 DDD 模型中的“用户”与身份验证用户集成?【英文标题】:How do integrate "Users" in my DDD model with authenticating users? 【发布时间】:2011-10-15 13:58:17 【问题描述】:

我正在创建我的第一个 ASP.NET MVC 站点,并且一直在尝试遵循域驱动的开发。我的站点是一个项目协作站点,用户可以在其中分配到站点上的一个或多个项目。然后将任务添加到项目中,并且可以将项目中的用户分配给任务。所以“用户”是我的领域模型的一个基本概念。

我的计划是创建一个“用户”模型对象,其中包含有关用户的所有信息,并且可以通过 IUserRepository 访问。每个用户都可以通过一个 UserId 来标识。虽然我现在不确定我是否希望 UserId 是字符串或整数。

我的域对象 User 和 IUserRepository 应该如何与我的站点的更多管理功能相关联,例如授权用户并允许他们登录?如何将我的域模型与 ASP.NET 的其他方面(例如 HttpContext.User、HttpContext.Profile、自定义 MemberShipProvider、自定义 ProfileProvider 或自定义 AuthorizeAttribute)集成?

我应该创建一个自定义的 MembershipProvider 和/或 ProfileProvider 来包装我的 IUserRepository 吗?虽然,我也可以预见为什么我可能想要将我的域模型中的用户信息与我网站上的用户授权分开。例如,将来我可能想从表单身份验证切换到 Windows 身份验证。

不要尝试重新发明***并坚持使用 ASP.NET 中内置的标准 SqlMembershipProvider 会更好吗?每个用户的配置文件信息将存储在域模型 (User/IUserRepository) 中,但这不包括他们的密码。然后我会使用标准的 ASP.NET 成员资格来处理创建和授权用户吗?因此,在创建帐户或首次登录时,需要在某处有一些代码知道在 IUserRepository 中为新用户创建配置文件。

【问题讨论】:

好问题,我很想看到体面的答案。大约 4 个月前,我在一个项目中解决了同样的问题,但我不确定我是否对最终采用的方法感到满意。 我会投票赞成这个问题,但它真的就像 10 个问题合二为一。提出具体可回答问题的最佳方法是开始制作一些原型,并决定学习这些系统中的每一个的努力是否值得你自己编写它们。两者都不是很困难,您可能应该让目标指导您的决定。 相关:***.com/questions/3964989/… 【参考方案1】:

是的 - 非常好的问题。和@Andrew Cooper 一样,我们的团队也经历了这一切。

我们采用了以下方法(正确或错误):

自定义成员资格提供程序

我或其他开发人员都不是内置 ASP.NET Membership 提供程序的粉丝。对于我们网站的内容(简单的、UGC 驱动的社交网站)来说,它太臃肿了。我们创建了一个非常简单的,可以满足我们应用程序的需要,仅此而已。而内置的成员资格提供程序可以满足您可能需要的所有功能,但很可能不会。

自定义表单身份验证票证/身份验证

我们应用程序中的所有内容都使用接口驱动的依赖注入(StructureMap)。这包括表单身份验证。我们创建了一个非常薄的界面:

public interface IAuthenticationService

   void SignIn(User user, HttpResponseBase httpResponseBase);
   void SignOut();

这个简单的界面可以轻松进行模拟/测试。通过实施,我们创建了一个自定义表单身份验证票证,其中包含:每个 HTTP 请求都需要的 UserId 和 Roles 等内容,它们不会经常更改,因此不应在每个请求上获取。

然后我们使用操作过滤器解密表单身份验证票证(包括角色)并将其粘贴在 HttpContext.Current.User.Identity 中(我们的 Principal 对象也是基于接口的)。

使用 [Authorize] 和 [AdminOnly]

我们仍然可以使用 MVC 中的授权属性。我们还为每个角色创建了一个。 [AdminOnly] 只是检查当前用户的角色,并抛出 401(禁止)。

简单的用户单表,简单的 POCO

所有用户信息都存储在一个表中(“可选”用户信息除外,例如个人资料兴趣)。这被映射到一个简单的 POCO(实体框架),它还在对象中内置了域逻辑。

用户存储库/服务

特定于域的简单用户存储库。诸如更改密码、更新配置文件、检索用户等。存储库调用我上面提到的用户对象上的域逻辑。该服务是存储库顶部的一个瘦包装器,它将单个存储库方法(例如 Find)分离为更专业的方法(FindById、FindByNickname)。

域与安全性分开

我们的“域”用户及其关联信息。这包括姓名、个人资料、facebook/社交整合等。

“Login”、“Logout”之类的东西处理身份验证,而“User.IsInRole”之类的东西处理授权,因此不属于域。

所以我们的控制器可以同时使用IAuthenticationServiceIUserService

创建配置文件是域逻辑的完美示例,它也与身份验证逻辑混合。

这是我们的样子:

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

总结

以上对我们来说效果很好。我喜欢拥有一个简单的 User 表,而不是 ASP.NET Membership 提供者那种臃肿的疯狂。它很简单,代表我们的,而不是 ASP.NET 对它的表示。

话虽如此,正如我所说,我们有一个简单的网站。如果您在银行网站上工作,那么我会小心重新发明***。

我的建议是先创建域/模型,然后再考虑身份​​验证。 (当然,这就是 DDD 的全部意义所在)。

然后制定您的安全要求并适当地选择身份验证提供程序(现成的或自定义的)。

不要让 ASP.NET 决定您的域应该如何设计。这是大多数人陷入的陷阱(包括我,在之前的项目中)。

祝你好运!

【讨论】:

谢谢!这个答案真的很有帮助。我仍在尝试将我脑海中的一切拼凑起来。当您谈到使用“自定义成员资格提供程序”时,您的意思是您实际上有一个继承自 System.Web.Security.MembershipProvider 并在 web.config 文件的成员资格元素中标识的自定义类?您能否也发布一些 POST 登录和注销操作方法的示例代码? @Eric - 不,它不是自定义会员提供程序。这是一个满足要求的自定义类。 ASP.NET 对此一无所知。它只关心 HttpContext.Request.IsAuthenticated。我们没有使用会员提供者 API。登录/注销操作方法很简单(表单身份验证登录、注销等) 如何创建您的身份验证票?您是否使用常规 ASP.NET FormsAuthentication 创建身份验证票证?换句话说,您的 IAuthorizationService 实现是否调用 FormsAuthentication.SetAuthCookie() 和 FormsAuthentication.SignOut()?还是通过调用 HttpResponseBase.AppendCookie() 手动添加 cookie。我很困惑为什么你的 IAuthenticationService 需要传递一个 HttpResponseBase? @Eric - 只需将 cookie(具有正确的表单身份验证名称)添加到响应中。猜猜它可能不需要接受响应,它可以只访问静态 httpcontext.current.response。我们这样做是有原因的,只是不记得了。 :) cookie 包含一个加密的FormsAuthenticationTicket +1 “不要让 ASP.NET 决定你的域应该如何设计。这是大多数人陷入的陷阱”实际上就是它的全部内容。【参考方案2】:

让我稍微分解一下你收集的问题:

虽然我现在不确定我是否希望 UserId 是字符串或整数。

它不一定是一个整数,但绝对可以在这里使用某种基于位的值(例如 int、long 或 guid)。在固定大小值上运行的索引比在字符串上运行的索引要快得多,而且在您的一生中,您永远不会用完用户的标识符。

我的域对象 User 和 IUserRepository 应该如何与我的站点的更多管理功能相关联,例如授权用户并允许他们登录?

决定是否要使用内置的 asp.net 成员资格。我建议不要因为它大多只是臃肿而且你必须自己实现它的大部分功能,比如电子邮件验证,你可以通过查看生成的表格来认为它会内置......模板ASP.NET MVC 1 和 2 的项目都包含一个简单的成员存储库,只需重写实际验证用户的函数,您就可以顺利进行。

如何将我的域模型与 ASP.NET 的其他方面(例如 HttpContext.User、HttpContext.Profile、自定义 MemberShipProvider、自定义 ProfileProvider 或自定义 AuthorizeAttribute)集成?

这些中的每一个都值得提出它自己的 SO 问题,并且之前已经在这里问过每个问题。话虽如此, HttpContext.User 仅在您使用内置的 FormsAuthentication 功能时才有用,我建议您在开始时使用它,直到遇到它不能满足您的要求的情况。我喜欢在使用FormsAuthentication 登录时将用户密钥存储在名称中,如果HttpContext.User.IsAuthenticatedtrue,则在每个请求的开头加载请求绑定的当前用户对象。

至于个人资料,我热衷于避免有状态的请求,并且以前从未使用过它,因此其他人将不得不帮助您。

使用内置[Authorize] 属性只需告诉FormsAuthentication 用户已验证。如果您想使用授权属性的角色功能,请编写您自己的RoleProvider,它会像魔术一样工作。你可以在 Stack Overflow 上找到很多这样的例子。 HACK:你只需要实现RoleProvider.GetAllRoles()RoleProvider.GetRolesForUser(string username)RoleProvider.IsUserInRole(string username, string roleName)就可以让它工作。除非您希望使用 asp.net 会员系统的所有功能,否则您不必实现整个界面。

不要尝试重新发明***并坚持使用 ASP.NET 中内置的标准 SqlMembershipProvider 会更好吗?

对于这个问题的每一个推导,务实的答案是不要重新发明***,直到***没有做你需要它做的事情,你需要它做的事情。

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;
    

【讨论】:

你的大脑执行底部代码块需要多长时间? @CRice:每次迭代的时间更少。 @CRice:投下票并解释原因,那么您将创造价值,否则您的 cmets 只是噪音。【参考方案3】:

我知道我的回答来得有点晚,但为了将来参考其他有相同问题的同事。

这里也是使用角色的自定义身份验证和授权的示例。 http://www.codeproject.com/Articles/408306/Understanding-and-Implementing-ASP-NET-Custom-Form。这是一篇非常好的文章,非常新鲜和最近。

在我看来,您应该将此实现作为基础架构的一部分(只需创建一个新项目 Security 或您想要的任何名称)并在那里实现这个示例。然后从你的应用层调用这个机制。请记住,应用程序层控制和编排应用程序中的整个操作。领域层应该只关注业务操作,而不是访问或数据持久性等。它不知道您如何验证系统中的人员。

想想实体公司。实施的指纹访问系统与该公司的运营无关,但它仍然是基础设施(建筑物)的一部分。事实上,它控制了谁可以访问公司,因此他们可以履行各自的职责。您没有两名员工,一名负责扫描他的指纹,以便另一名可以走进来完成工作。你只有一个有食指的员工。对于“访问”,您只需要他的手指......因此,如果您要使用相同的 UserRepository 进行身份验证,您的存储库应该包含一种身份验证方法。如果您决定改用 AccessService(这是一项应用程序服务,而不是域服务),您需要包含 UserRepository 以便您访问该用户数据,获取他的手指信息(用户名和密码)并将其与来自的任何内容进行比较表格(手指扫描)。我解释得对吗?

DDD 的大多数情况都适用于现实生活中的情况......当涉及到软件架构时。

【讨论】:

以上是关于如何将我的 DDD 模型中的“用户”与身份验证用户集成?的主要内容,如果未能解决你的问题,请参考以下文章

您如何将身份验证、角色和安全性融入您的 DDD?

如何在实时数据库中将用户 ID 设置为与身份验证中的用户 ID 相同? [复制]

如何更改 laravel 中的用户模型以使用其他表进行身份验证而不是用户?

使用身份验证 ASP.NET MVC 中的用户

没有用户模型的 Laravel JWT 身份验证

如何将 Django 的身份验证用户模型配置为具有 UUID(Postgres DB)