Pull to refresh

Comments 46

По-моему, адский велосипед. Еще и в области безопасности, где за каждый баг (а у вашей реализации, я уверен, их больше, чем у стандартной) могут что-нибудь оторвать. ИМХО, реализация (Extended)MembershipProvider для нужд конкретного домена будет гораздо проще.

Особо внимательно код не смотрел, так что извините, если упустил какую-то критически важную деталь.
Согласен, возможно и велосипед. Но MembershipProvider очень громоздкий и не удобный в изпользовании. Он засоряет бизнес локигу моделей пользователя. Мне кажется, для маленьких проектов он не очень удобен.
Что вам мешает реализовать только нужные методы и выбрасывать NotImplementedException из ненужных?
На самом деле не чего не мешает. Просто перегруженность интерфесса, засорение бизнес логики приложения, возможно меня это оттолкнуло от методов из коробки.
Simple Membership Provider пробовали?
На момент написание статьи не находил не чего такого. Сейчас почитаем.
он появился в MVC4. менее функциональный чем SqlMembershipProvider, но гораздо проще в использовании и так же поддерживает ролевую модель доступа.
А Windows Identity Foundation чем не устроил?
Я о нем к сожалению не слышал. Поставил в TODO с высшим приоритетом.
Password Authentication — а где можно почитать про этот провайдер? Или имелось ввиду Passport Authentication?
Сложная и устаревшая штуковина :(
Впервые была представлена в Windows XP, но распространения она не получила, ввиду отсутствия простой кросс-платформенности, до Windows 8 эта штука затаилась, и в конечном итоге вылилась в принудительный Windows Live Account. :)

Passport Authentication сводилось к тому, что пользователь сам должен был решать какие данные предоставлять для аутентификации, а удалённая система уже сама решала, достаточно ей этого или нет.

Вы случайно не о CardSpace говорите?
Помойму да о ней. Скоерей всего Jholinar ошибся в названии.
Вообще всегда реализовывал MembershipProvider с перегрузками, никогда никаких особых порблем не испытывал. Вполне себе проверенный практикой и секьюрный метод аутендификации. Писать свой способо с нуля требует времени и вероятность получить дырявый порвайдер. Зачем на это тратить время, если есть проверенные вещи?
С MembershipProvider'ом — одни проблемы, он очень сложный и зачастую не нужный. Все реализации всегда потом сводятся к следующему:

var provider = ((MyMembershipProvider)GetMembershipProvider());
provider.GetVipUsers();


Другими словами, расширять этот механизм в ASP.NET смысла нет ни какого. Либо в чистом виде как есть, либо WIF и если всё не подходит — реализовывать самостоятельно.
Понимаю, что это всего лишь пример, но конкретно для этой функциональности ведь есть RoleProvider, который так же легко имплементируется.
Ладно бы легко имплементируется, так ведь он еще и после этого работает со всеми стандартными механизмами (что уменьшает количество велосипедов вверх по стеку).
Ну стек то сейчас не особо на них завязан. Особенно если использовать WIF. Другой вопрос, что если не знаете как правильно хранить те же самые пароли — лучше не трогать и оставить дефолт =)
Соглашусь с tp7: Адский велосипед :)

Во-первый, вы реализовали ACL based security на базе Role Based Security — гремучая смесь.
Для ACL based security уже давно существует WIF (Windows Identity Foundation)

Во-вторых, вы спутали аутентификацию и авторизацию — это всё-таки разные вещи.

public interface IIdentity
{
    string AuthenticationType { get; }
    bool IsAuthenticated { get; }
    string Name { get; }
}



IIdentity отвечает на вопрос: Кто ты такой и как тебя зовут

А вот:
public interface IPrincipal
{
   bool IsInRole(string role);
   IIdentity Identity { get; }
}


отвечает на вопрос: Чем мы владеем, т.е. какие роли у нас существуют.

Другими словами, ваш IRule — это ClaimsAuthorizationManager

public interface IAuthorizeService<in TAccount>
{
        void SignIn(TAccount account, bool createPersistentCookie);
        void SignOut();
}


И так, SignIn & SignOut — это методы аутентификации, но уж точно не авторизации, как я сказала выше.

Теперь, если мы перейдём к Windows Identity Foundation, то мы откроем для себя много интересного:
1. Пользователь может иметь больше чем один способ аутентификации, в реальности, это может быть аутентификация в нашей системе + ещё всякие там токены от соц. сетей или ещё от чего-то там

2. Зачастую аутентифицированый субъёкт хочет аутентифицироваться ещё где-то, т.е. провести процедуру ActAs и для этого ему нужен будет bootstrap token

3. и так далее

Это я к тому, что вы потратили время на написание фабрики по выпуску абстратных фабрик, к тому же наследие в вашей реализации — всё сильно усложняет, и практически не даст вам возможности изменить систему аутентификации и авторизации.

Фактически, всё что вы тут нагородили, сводится к тому, что для доступа к ресурсам вам нужно проверять наличие тех или иных аттрибутов у аутентифицированого субъёкта.

Для этого достаточно было привести это всё дело к виду: Один ресурс — один аттрибут (claim), с правами read/write/delete или ещё что вам там нужно.

Implement custom Claim based Authorization in ASP.NET MVC Web Application

Я просто частично процитиру:
Authorization

The first thing to identify is to what types of claims make sense for our fictitioushospital.com website. Lets think of claims as permissions that are required to access the features. If we consider patients as resources we may have (CRUD) permissions like Patient Create, Patient View, Patient Edit and Patient Delete. So the possible list of claims may be:

1. Patient Create

Right: schemas.xmlsoap.org/ws/2005/05/identity/right/possessproperty
Claim Type: schemas.fictitioushospital.com/2010/02/identity/claims/create
Resources: schemas.fictitioushospital.com/2010/02/identity/resources/patients

2. Patient View

Right: schemas.xmlsoap.org/ws/2005/05/identity/right/possessproperty
Claim Type: schemas.fictitioushospital.com/2010/02/identity/claims/read
Resources: schemas.fictitioushospital.com/2010/02/identity/resources/patients

3. Patient Edit

Right: schemas.xmlsoap.org/ws/2005/05/identity/right/possessproperty
Claim Type: schemas.fictitioushospital.com/2010/02/identity/claims/update
Resources: schemas.fictitioushospital.com/2010/02/identity/resources/patients

4. Patient Delete

Right: schemas.xmlsoap.org/ws/2005/05/identity/right/possessproperty
Claim Type: schemas.fictitioushospital.com/2010/02/identity/claims/delete
Resources: schemas.fictitioushospital.com/2010/02/identity/resources/patients


И в завершение:
How To: Build Claims-Aware ASP.NET MVC Web Application Using WIF
Вы меня уделали. Да к сожалению, как то проглядел я WIF :(. Не чего не буду говорить за и против, так как не использовал.

Во-вторых, вы спутали аутентификацию и авторизацию — это всё-таки разные вещи.

Из перечисленого вами, у меня реализована и та и другая часть. Разве нет?

Спасибо огромной количесво ссылок, есть над чем задуматся.
Верно, вы реализовали и то и другой но как единое целое, а это два не зависимых элемента, которые используют друг друга.

Есть правило первого события: аутентификация а потом авторизация.
Отсутствие аутентификации — это тоже аутентификация, только анонимного субъекта, для этого и существует свойство в IIdentity:IsAuthenticated
А что Вы можете сказать по поводу permission-based системы безопасности? Ну т.е. когда у нас вместо claim (например, ресурс — Пользователь, действие/claim type — Создать, Удалить, и т.д.) есть простые идентификаторы (например, СоздатьПользователя). Мне кажется, что разница между ними не столь значительна, за исключением того, что в базовой библиотеке уже есть некая реализованная модель, завязанная на claims.

Лично мне она импонирует простотой интеграции с ASP.NET MVC при помощи action filters. Если мы хотим, чтобы действие Index у контроллера Projects было доступно только некоторым пользователям, то мы помечаем это действием самописным атрибутом [RequiresPermissions(Permissions.ReadProjects)], а конкретным пользователям назначаем полномочие ReadProjects.

Во время авторизации запроса экземпляр IPrincipal подменяется нашей собственной реализацией, которая запрашивает из БД набор полномочий для текущего IIdentity. Далее сравниваем полномочия пользователя и полномочия, запрошенные controller action-ом.

Хочу отметить, что я не пытаюсь сравнить роли с permissions/claims, они предоставляют идеологически разные модели обеспечения безопасности.
А теперь попробуйте добавить галочку в настройки типа — «Не писать лог аудита для операций чтения». Становится не очень удобно. Так же если у вас ресурсов не 10 а 100, то получите вместо 5 действий и 100 ресурсов — 500 permissions в явном виде.

Вы кстати так же можете сделать:
[ClaimsAuthorize(Action.Index, Resource.Project)]
Соглашусь в этом с Вами, разделение прав доступа по типу операций имеет свои преимущества. Однако не все операции можно выразить в терминах CRUD, а поддержка двух независимых множеств T (тип операции) и R (ресурс) означает существование некоторых пар (T, R), которых наша доменная область не предусматривает.

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

По поводу атрибутов — конечно же, можно так сделать, но если для некоторого controller action-а мне надо было указать несколько permissions, то я мог их записать в одном атрибуте. Если мне надо несколько claims, то придётся помечать метод соответствующим числом атрибутов. Разница не столь существенная, но первый вариант выглядит более компактно и приятно.
public ClaimsAuthorize(string action, string resource, params string[] additionalResources) ;)
Экшены ведь могут быть разные.
Практически все можно разложить в ресурсную модель и сократить количество действий до минимума.
Итак, нашей целью будет написание настраиваемой (кастомной) аутентификации, с небольшой системой безопасности на основе ролей. Она не будет требовать изменения или дополнения модели предметной области приложения. Основное требование, это какой либо намек на пользователей и систему ролей. Стандартную систему безопасности, основанную на membership провайдерах, мы не будем рассматривать, и тем более использовать. Она мне кажется совсем не удобной. Основной ее минус заключается в необходимости конкретной схемы данных, которую зачастую сложно связать с предметной областью вашего приложения

Типичный такой NIH. Разбираться в MembershipProvider и RoleProvider вам просто было лень, иначе бы вы знали, что на схему данных ему полностью плевать.

Зато после этого работают стандартный Authorize(Roles = ...), безо всяких прыжков и ужимок.

В реализации подписанного метода декадируем cookie, создав и записав пользователя в текщий HTTP запрос.

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

Незабываем о unit-тестировании.

Это шутка такая, при вашем-то количестве обращений к глобальным контекстам (HttpContext.Current, FormsAuthentication)?

Ну вот например:

protected AbstractController()
{
            _user = new Lazy<TIdenty>(() => HttpContext.User.Identity as TIdenty);
}


Про наличие у контроллера свойства ControllerContext вы, видимо, не в курсе?

В общем, наглядная демонстрация того, почему не надо, все-таки, писать велосипеды.
Соглашусь и тут, человек полез в очень сложную область, не разобравшись даже в том, чем отливается авторизация от аутентификации, как и зачем придумали ControllerContext, Thread.CurrentPrincipal, HttpContextBase и так далее.
Признаю во многом не прав.
Но MembershipProvider и RoleProvider как уже сказали выше. + еще любую ошибку, опечатку с предоставлением ролей можно обнаружить только в момент выполнения. Так как роли записаны в строковом формате. Так же усложняется процесс рефакторинга.

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


Кто вам мешает добавить нужные правила? прим. Вытаскивам по id юзера, и проверяем его роли.

Про наличие у контроллера свойства ControllerContext вы, видимо, не в курсе?


Так вроде при обращении через ContorollerContext.HttpContext достает тот же самый индивидуальный HTTP запрос?
еще любую ошибку, опечатку с предоставлением ролей можно обнаружить только в момент выполнения. Так как роли записаны в строковом формате.

Вы про константы не слышали?

Кто вам мешает добавить нужные правила? прим. Вытаскивам по id юзера, и проверяем его роли.

Мне никто не мешает, но зачем это делать, если это уже реализовано в фреймворке (и зачем тогда хранить всю информацию о пользователе в UserData, которая еще и не безгранична)?

Так вроде при обращении через ContorollerContext.HttpContext достает тот же самый индивидуальный HTTP запрос?

Нет, совсем не тот же самый — а тот, который был задан при создании контекста.
UserData — не такой уж и простой объект, как может показаться. Внутри он использует CallContext, и единственное для чего его можно использовать это только для кэширования объектов в пределах одного запроса, т.к. ASP.NET гарантирует, что CallContext будет создаваться для каждого запроса.

Использовать UserData (CallContext) нужно, т.к. [ThreadStatic] не гарантирует ровным счётом ни чего, и разные запросы могут обрабатываться разными потоками, так же IIS может создавать Application Pool что приводит к ошибкам при использовании статитических переменных.

ThreadStatic, CallContext and HttpContext in ASP.Net
Я про UserData в куке FormsAuthentication, куда автор поста пишет сериализованного принципала. Это явно не то, о чем вы говорите.
Ой… Ошиблась) Ну ни чего, будет всем доп. информация, мне не жалко)
Вы про константы не слышали?

Слышал)
Ну есть же вероятность, что такой как я «иноватор» не слышал.
Тогда вот вам другой способ: юнит-тестирование :)

И если уж совсем жёстко, то можно отобрать права на check-in на файл с константами, если есть вероятность большого количества «новаторов».
А ad+anonymous не настраивали?? Очень острый вопрос. Гугл дает больше вопросов чем ответов.
Я делал AD + Form — писал свой MembershipProvider. Там можно было быть не авторизованным анонимусом. Или вы о чём то другом?
Хм. Что мешает слить исходники mvc4, доступные открыто, посмотреть на то, как у них это все реализовано и не городить вот такое самостоятельно?
В исходниках, кстати много очень интересного.
Но по авторизации я далеко не лез, только немного подправил нужные мне методы — опять же, при обновлении версии mvc переносимость будет лучше.
Помойму править исходники, это совсем не выход. При последующим изменении MVC, заново править?
Отслеживать изменения не вариант?
Помойму дополнительная головная боль. Для каждого проекта иметь свой MVC Framework, с настроеной для него системой аторизации, аутентификации?
На самом деле кому, как удобней. Не буду спорить.
Хм. Вообще-то MVC и так для каждого проекта ложится отдельно и отдельно в каждом проекте обновляются nuget'ом
Не правильно я мысль изложил. Я про то что, если у каждого проекто свои особенности в аторизации, аутентификации. получается каждый раз преписывать под определенный проект.
Не знаю, как у Вас, а среди моего окружения большая часть тех вещей, которыми пользуется ентерпрайз тяготеет к централизованной авторизации и аутентификации в виде AD/LDAP или еще каких технологий, AD и Windows авторизация поддерживается из коробки, тоже в общем, вместе с ролевой моделью.
Я вот тут уже одну статью написал про mvc и авторизацию (в профиле). Не без говнокода, конечно, но все никак не дойдут руки переписать статью по человечески — например на использования sitemap'а и привязки его к текущей роли пользователя в системе
Sign up to leave a comment.

Articles