Как интегрировать «Пользователи» в моей модели DDD с аутентификацией пользователей? - PullRequest
39 голосов
/ 29 июля 2011

Я создаю свой первый сайт ASP.NET MVC и пытаюсь следить за развитием домена. Мой сайт - это сайт совместной работы над проектами, на котором пользователи могут быть назначены одному или нескольким проектам на сайте. Затем задачи добавляются в проекты, и пользователи в проекте могут быть назначены задачам. Таким образом, «Пользователь» - это фундаментальная концепция модели моего домена.

Мой план состоит в том, чтобы иметь объект модели «Пользователь», который содержит всю информацию о пользователе и может быть доступен через IUserRepository. Каждый пользователь может быть идентифицирован по идентификатору пользователя. Хотя на данный момент я не уверен, хочу ли я, чтобы UserId был строкой или целым числом.

Каким образом мои доменные объекты User и IUserRepository должны соотноситься с более административными функциями моего сайта, такими как авторизация пользователей и разрешение им входить в систему? Как мне интегрировать мою модель домена с другими аспектами ASP.NET, такими как HttpContext.User, HttpContext.Profile, пользовательским MemberShipProvider, пользовательским ProfileProvider или пользовательским AuthorizeAttribute?

Должен ли я создать собственный MembershipProvider и / или ProfileProvider, который оборачивает мой IUserRepository? Хотя я также могу предвидеть, почему я могу отделить информацию о Пользователе в моей модели домена от авторизации пользователя на моем сайте. Например, в будущем я, возможно, захочу переключиться на проверку подлинности Windows с форм проверки подлинности.

Было бы лучше не пытаться изобретать велосипед и придерживаться стандартного SqlMembershipProvider, встроенного в ASP.NET? Информация о профиле каждого пользователя будет храниться в модели домена (User / IUserRepository), но это не будет включать их пароль. Я бы тогда использовал стандартный материал ASP.NET для создания и авторизации пользователей? Поэтому где-то должен быть какой-то код, который бы знал, чтобы создать профиль для новых пользователей в IUserRepository при создании их учетной записи или при первом входе в систему.

Ответы [ 3 ]

15 голосов
/ 29 июля 2011

Да - очень хороший вопрос. Как и @ Эндрю Купер, наша команда тоже прошла через все это.

Мы использовали следующие подходы (правильные или неправильные):

Пользовательский членство

Ни я, ни другой разработчик не поклонники встроенного поставщика членства ASP.NET. Он слишком раздут для того, что представляет собой наш сайт (простой социальный сайт, управляемый UGC). Мы создали очень простой, который делает то, что нужно нашему приложению, и ничего более. Принимая во внимание, что встроенный поставщик членства делает все, что может нужно, но, скорее всего, не будет.

Билет / Аутентификация с использованием пользовательских форм

Все в нашем приложении использует внедрение зависимостей через интерфейс (StructureMap). Это включает проверку подлинности с помощью форм. Мы создали очень тонкий интерфейс:

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

Этот простой интерфейс позволяет легко макетировать / тестировать. С помощью этой реализации мы создаем пользовательский билет проверки подлинности форм, содержащий: такие вещи, как UserId и роли, которые требуются при каждом HTTP-запросе, изменяются не часто и, следовательно, не должны выбираться при каждом запросе.

Затем мы используем фильтр действий для расшифровки билета проверки подлинности форм (включая роли) и вставляем его в HttpContext.Current.User.Identity (для которого наш объект Principal также основан на интерфейсе).

Использование [Авторизоваться] и [AdminOnly]

Мы все еще можем использовать атрибуты авторизации в MVC. И мы также создали один для каждой роли. [AdminOnly] просто проверяет роль для текущего пользователя и выбрасывает 401 (запрещено).

Простая таблица для пользователя, простая POCO

Вся пользовательская информация хранится в одной таблице (за исключением «необязательной» информации о пользователе, такой как интересы профиля). Это сопоставлено с простым POCO (Entity Framework), который также имеет доменную логику, встроенную в объект.

Репозиторий пользователей / Сервис

Простой пользовательский репозиторий, который зависит от домена . Такие вещи, как изменение пароля, обновление профиля, получение пользователей и т. Д. Хранилище вызывает логику домена для объекта User, о котором я упоминал выше. Служба представляет собой тонкую оболочку поверх хранилища, которая разделяет отдельные методы хранилища (например, Find) на более специализированные (FindById, FindByNickname).

Домен отделен от безопасности

Наш «домен», информация о пользователе и его / ее ассоциации. Это включает в себя имя, профиль, Facebook / социальную интеграцию и т. Д.

Такие вещи, как «Логин», «Выход из системы» имеют дело с аутентификацией и такие вещи, как «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. Это просто и представляет наш домен , а не его представление в ASP.NET.

Как говорится , как я уже сказал, у нас простой сайт. Если вы работаете на банковском веб-сайте, я бы позаботился о том, чтобы заново изобрести колесо.

Мой совет: сначала создайте свой домен / модель, прежде чем даже подумать об аутентификации. (конечно, это то, что DDD это все).

Затем разработайте ваши требования безопасности и выберите поставщика аутентификации (готового или обычного) соответствующим образом.

Не позволяйте ASP.NET определять, как должен создаваться ваш домен.Это ловушка, в которую попадают большинство людей (включая меня, в предыдущем проекте).

Удачи!

4 голосов
/ 29 июля 2011

Позвольте мне немного разбить вашу коллекцию вопросов:

Хотя на данный момент я не уверен, хочу ли я, чтобы UserId был строкой или целым числом.

Это не обязательно должно быть целое число, скажем, но здесь определенно используется какое-то битовое значение (например, int, long или guid).Индекс, работающий с фиксированным значением размера, намного быстрее, чем индекс по строке, и в течение вашей жизни у вас никогда не кончатся идентификаторы для ваших пользователей.

Как следуетобъекты домена User и IUserRepository относятся к более административным функциям моего сайта, таким как авторизация пользователей и предоставление им возможности входа в систему?

Решите, хотите ли вы использовать встроенное членство asp.net или нет,Я рекомендую не по той причине, что в большинстве случаев это просто вздор, и вам все равно придется реализовать большинство его функций, таких как проверка электронной почты, о чем вы могли бы подумать, глядя на сгенерированные таблицы, в которые он будет встроен ... ШаблонПроект для ASP.NET MVC 1 и 2 включает в себя простой репозиторий членства, просто перепишите функции, которые фактически проверяют пользователя, и вы будете в пути.

Как бы яинтегрировать мою модель домена с другими аспектами ASP.NET, такими как HttpContext.User, HttpContext.Profile, пользовательским MemberShipProvider, пользовательским ProfileProvider или пользовательским AuthorizeAttribute?

Каждый из них являетсядостойный своего собственного ТАКОГО вопроса, и каждый был задан здесь прежде.При этом HttpContext.User полезен только в том случае, если вы используете встроенную функциональность FormsAuthentication, и я рекомендую использовать ее в начале, пока вы не столкнетесь с ситуацией, когда она не делает то, что вам нужно.Мне нравится хранить ключ пользователя в имени при входе в систему с помощью FormsAuthentication и загружать привязанный к запросу текущий объект пользователя в начале каждого запроса, если HttpContext.User.IsAuthenticated равен true.

Что касается профиля,Я страстно избегаю запросов о состоянии и никогда раньше не использовал их, поэтому кто-то другой должен помочь вам с этим.

Все, что вам нужно для использования встроенного атрибута [Authorize], это сообщить FormsAuthentication пользователь действителен.Если вы хотите использовать функцию ролей атрибута authorize, напишите свой RoleProvider, и он будет работать как по волшебству.Вы можете найти множество примеров для этого в Stack Overflow.HACK: вам нужно только реализовать RoleProvider.GetAllRoles(), RoleProvider.GetRolesForUser(string username) и RoleProvider.IsUserInRole(string username, string roleName), чтобы это работало.Вам не нужно реализовывать весь интерфейс, если вы не хотите использовать все функциональные возможности системы членства asp.net.

Было бы лучше не пытаться заново изобретать колесо ипридерживайтесь стандартного SqlMembershipProvider, встроенного в ASP.NET?

Прагматичный ответ на каждый вывод этого вопроса - не изобретать колесо, пока колесо не сделает то, что вам нужноделай, как тебе это нужно.

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;
    }
}
1 голос
/ 05 ноября 2012

Я знаю, что мой ответ приходит с небольшим опозданием, но для будущих ссылок на других коллег, имеющих тот же вопрос.

Вот также пример пользовательской аутентификации и авторизации с использованием ролей.http://www.codeproject.com/Articles/408306/Understanding-and-Implementing-ASP-NET-Custom-Form. Это очень хорошая статья, очень свежая и свежая.

По моему мнению, у вас должна быть эта реализация как часть инфраструктуры (просто создайте новый проект Security или как вы хотите его называть) и реализуйте этот пример выше.Затем вызовите этот механизм с вашего уровня приложений.Помните, что прикладной уровень контролирует и управляет всей операцией в вашем приложении.Уровень домена должен заботиться исключительно о бизнес-операциях, а не о доступе или сохранении данных и т. Д. Он не знает, как вы аутентифицируете людей в вашей системе.

Подумайте о компании по производству кирпича и раствора.Внедренная система доступа по отпечаткам пальцев не имеет ничего общего с деятельностью этой компании, но, тем не менее, она является частью инфраструктуры (здания).На самом деле, он контролирует, кто имеет доступ к компании, чтобы они могли выполнять свои соответствующие обязанности.У вас нет двух сотрудников, один сканирует отпечатки пальцев, чтобы другой мог войти и выполнить свою работу.У вас есть только Сотрудник с указательным пальцем.Для «доступа» все, что вам нужно, это его палец ... Итак, ваш репозиторий, если вы собираетесь использовать тот же UserRepository для аутентификации, должен содержать метод для аутентификации.Если вы решили вместо этого использовать AccessService (это служба приложения, а не служба домена), вам необходимо включить UserRepository, чтобы получить доступ к данным этого пользователя, получить информацию о его пальце (имя пользователя и пароль) и сравнить его с тем, что исходит отформа (сканирование пальцев).Правильно ли я объяснил себя?

Большинство ситуаций DDD применимы к ситуациям реальной жизни ... когда речь идет об архитектуре программного обеспечения.

...