如何將我的 DDD 模型中的“使用者”與身份驗證使用者集成?
我正在創建我的第一個 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 中為新使用者創建配置文件。
是的 - 非常好的問題。像@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 對像也是基於介面的)。[Authorize] 和 [AdminOnly] 的使用
我們仍然可以使用 MVC 中的授權屬性。我們還為每個角色創建了一個。
[AdminOnly]只需檢查目前使用者的角色,並拋出 401(禁止)。簡單的使用者單表,簡單的 POCO
所有使用者資訊都儲存在一個表中(“可選”使用者資訊除外,例如個人資料興趣)。這被映射到一個簡單的 POCO(實體框架),它還具有內置到對像中的域邏輯。
使用者儲存庫/服務
特定於域的簡單使用者儲存庫。諸如更改密碼、更新配置文件、檢索使用者等。儲存庫呼叫我上面提到的使用者對像上的域邏輯。該服務是儲存庫頂部的一個瘦包裝器,它將單個儲存庫方法(例如 Find)分離為更專業的方法(FindById、FindByNickname)。
域與安全性分離
我們的“域”使用者及其關聯資訊。這包括姓名、個人資料、臉書/社交整合等。
“Login”、“Logout”之類的東西處理身份驗證,而“User.IsInRole”之類的東西處理授權,因此不屬於域。
所以我們的控制器可以同時使用
IAuthenticationService和IUserService。創建配置文件是域邏輯的完美範例,它也與身份驗證邏輯混合。
這是我們的樣子:
[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 決定您的域應該如何設計。這是大多數人陷入的陷阱(包括我,在以前的項目中)。
祝你好運!