如何在我的DDD模型中将“用户”与认证用户集成?

时间:2011-07-28 23:59:33

标签: asp.net asp.net-mvc authentication domain-driven-design

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

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

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

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

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

3 个答案:

答案 0 :(得分:15)

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

我们选择了以下方法(对或错):

  

自定义成员资格提供程序

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

  

自定义表单身份验证故障单/身份验证

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

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

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

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

  

使用[授权]和[AdminOnly]

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

  

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

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

  

用户存储库/服务

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

  

从安全分离的域名

我们的“域”用户和他/她的关联信息。这包括姓名,个人资料,脸书/社交整合等。

“登录”,“退出”等内容正在处理身份验证,而“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);
}
  

<强>摘要

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

据说,正如我所说,我们有一个简单的网站。如果你正在银行网站上工作,那么我会小心重新发明轮子。

我的建议是在您考虑身份验证之前先创建您的域/模型。 (当然,这就是DDD的全部内容)。

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

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

祝你好运!

答案 1 :(得分:4)

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

  
    

虽然我现在还不确定我是否希望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用户已被取消。如果您想使用authorize属性的角色功能,请编写自己的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;
    }
}

答案 2 :(得分:1)

我知道我的答案有点晚了,但是对于将来提到其他有同样问题的同事来说。

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

在我看来,您应该将此实现作为基础结构的一部分(只需创建一个新的项目安全性或任何您想要调用它)并在那里实现上面的示例。然后从您的应用层调用此机制。请记住,Application层控制并协调应用程序中的整个操作。域层应该完全关注业务操作,而不是关于访问或数据持久性等。它对如何验证系统中的人员一无所知。

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

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