Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Пример «claims-based» авторизации с «xml-based» конфигурацией политики доступа

Anna | 17.06.2014 | нет комментариев

Вступление

Тема аутентификации и авторизации неизменно будет востребована для большинства web-приложений. Многие .NET разработчики теснее поспели познакомиться с Windows Identity Foundation (WIF), его подходами и вероятностями для реализации так называемых «identity-aware» приложений. Для тех, кто не поспел поработать с WIF, первое знакомство дозволено начать с постижения дальнейшего раздела MSDN. В данной же статье я предлагаю больше подробно взглянуть на так называемый «claims-based» подход к авторизации пользователей путем постижения того, как это может выглядеть на примере.

Claims-Based Authorization

«Claims-Based» авторизация это подход, при котором решение авторизации о предоставлении либо закроете доступа определенному пользователю основывается на произвольной логике, которая в качестве входных данных использует некоторый комплект «claims» относящихся к этому пользователю. Проводя параллель с «Role-Based» подходом, у некого менеджера в его комплекте «claims» будет только один элемент с типом «Role» и значением «Administrator», скажем. Больше подробно, о превосходствах и загвоздках, которые решает данный подход дозволено прочесть на том же MSDN, также советую посмотреть лекцию Доминика Байера.

В целом, вышеупомянутый подход поощряет разработчиков к распределению бизнес логики приложения от логики авторизации и это подлинно комфортно. Так как же это выглядит на практике? К ней собственно и приступим.

Постановка задачи

Представим, что необходимо сделать некоторый API сервис, тот, что будет доступен нескольким клиентским приложениям. Функционал у клиентских приложений различный, пользователи также. Допустимо, появятся еще и другие клиентские приложения, со своими пользователями и схемой взаимодействия с API, следственно нам нужно иметь эластичную систему авторизации для того, Дабы иметь вероятность на любом этапе сконфигурировать политику доступа к API для того либо другого приложения/пользователя. API в нашем случае будет построено с применением ASP.NET Web API 2.0, клиентскими приложениями будут, скажем, Windows Phone приложение и Web-сайт.

Разглядим приложения их пользователей и функционал больше подробно:

Windows Phone заказчик

Windows Phone

  1. Сам по себе может только регистрировать новых пользователей.
  2. Зарегистрированные пользователи могут:
    • просматривать свой профиль;
    • обновлять свой профиль;
    • изготавливать смену своего пароля;
Web заказчик

Web Site

  1. Сам по себе не имеет доступа к API.
  2. Зарегистрированные мобильным заказчиком пользователи могут:
    • просматривать свой профиль;
    • обновлять свой профиль;
    • изготавливать смену своего пароля;
  3. Менеджеры системы могут:
    • всё то же, что и пользователи для своего аккаунта;
    • всё то же, что и пользователи для аккаунта всякого пользователя;
    • просматривать список всех зарегистрированный пользователей;
    • создавать/удалять пользователей;

Выходит, мы имеем представление о том, какой функционал должен предоставляться через API, каким заказчикам и с какими правилами. Что ж, приступим к реализации!

Реализация

Начнем с определения интерфейса грядущего API обслуживания:

    public interface IUsersApiController
    {
        // List all users.
        IEnumerable<User> GetAllUsers();

        // Lookup single user.
        User GetUserById(int id);

        // Create user.
        HttpResponseMessage Post(RegisterModel user);

        // Restore user's password.
        HttpResponseMessage RestorePassword(string email);

        // Update user.
        HttpResponseMessage Put(int id, UpdateUserModel value);

        // Delete user.
        HttpResponseMessage Delete(string email);
    }

Непосредственную реализацию API оставим за скобками данной статьи, по крайней мере, для примера сойдет и вариант как бы этого:

    public class UsersController : ApiController
    {
        //...
        public HttpResponseMessage Post([FromBody]RegisterModel user)
        {
            if (ModelState.IsValid)
            {
                return Request.CreateResponse(HttpStatusCode.OK, "Created!");
            }
            else
            {
                return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
            }
        }
        //...
    }

Дальнейшим шагом сотворим преемника класса ClaimsAuthorizationManager и переопределим некоторые его способы. ClaimsAuthorizationManager — это именно тот компонент WIF, тот, что разрешает в одном месте перехватывать входящие запросы и исполнять произвольную логику, которая исходя из комплекта «claims» нынешнего пользователя* решает о предоставлении либо закроете доступа.

* — о том, где данный комплект формируется побеседуем чуть позднее.

Не уходя вдалеке, мы можем позаимствовать его реализацию из MSDN по этой ссылке. Как видим из сегменты «Examples» переопределены следующие способы:

    /// <summary> 
    /// Overloads  the base class method to load the custom policies from the config file 
    /// </summary> 
    /// <param name="nodelist">XmlNodeList containing the policy information read from the config file</param>
    public override void LoadCustomConfiguration(XmlNodeList nodelist)
    {...}

    /// <summary> 
    /// Checks if the principal specified in the authorization context is authorized 
    /// to perform action specified in the authorization context on the specified resource 
    /// </summary> 
    /// <param name="pec">Authorization context</param>
    /// <returns>true if authorized, false otherwise</returns>
    public override bool CheckAccess(AuthorizationContext pec)
    {...}

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

   ...
   <policy resource="http://localhost:28491/Developers.aspx" action="GET">
     <or>
       <claim claimType="http://schemas.microsoft.com/ws/2008/06/identity/claims/role" claimValue="developer" />
       <claim claimType="http://schemas.xmlsoap.org/claims/Group" claimValue="Administrator" />
     </or>
   </policy>
   <policy resource="http://localhost:28491/Administrators.aspx" action="GET">
     <and>
       <claim claimType="http://schemas.xmlsoap.org/claims/Group" claimValue="Administrator" />
       <claim claimType="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/country" claimValue="USA" />
     </and>
   </policy>
   <policy resource="http://localhost:28491/Default.aspx" action="GET">
   </policy>
   ...

Политика доступа тут — это комплект сегментов «policy», всякая из которых идентифицируется такими признаками как «resource» и «action». Внутри всякой такой сегменты перечислены «claims» которые нужны для доступа к источнику. В случае WebApi «resource» — это имя контроллера, «action» — имя action-способа. Больше того, есть вероятность строить правила доступа с применением логических условий*.

* — и всё бы восхитительно если бы в нынешней реализации была вероятность конфигурировать огромнее 2-x элементов «claim» внутри блоков «and» либо «or».

Пока используем всё «as-is», за исключением наименования преемника, его изменим наXmlBasedAuthorizationManager. Если испробовать сбилдить план, то окажется что нам не хватает классаPolicyReader, его дозволено взять из полных начальных кодов MSDN-примера.

Позже того, как новая реализация готова, сконфигурируем WebAPI приложение для применения ее в качестве администратора авторизации. Для этого:

1. Зарегистрируем конфигурационные сегменты непременные для работы WIF:

  <configSections>
    <section name="system.identityModel" type="System.IdentityModel.Configuration.SystemIdentityModelSection, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" />
    <section name="system.identityModel.services" type="System.IdentityModel.Services.Configuration.SystemIdentityModelServicesSection, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" />
    <!-- Others sections-->
  </configSections>

2. Укажем какую реализацию следует применять в качестве администратора авторизации:

  <system.identityModel>
    <identityConfiguration>
      <claimsAuthorizationManager type="YourProject.WebApi.Security.XmlBasedAuthorizationManager, YourProject.WebApi, Version=1.0.0.0, Culture=neutral">
        <!-- Policies -->
      </claimsAuthorizationManager>
      <claimsAuthenticationManager type="YourProject.WebApi.Security.AuthenticationManager, YourProject.WebApi, Version=1.0.0.0, Culture=neutral" />
    </identityConfiguration>
  </system.identityModel>

Отменно, мы указали WIF какую реализацию применять, но как вы подметили, в конфигурации выше остались две детали:

    1. взамен комплекта xml-сегментов «policy» у нас пусто;
    2. присутствует xml-элемент “claimsAuthenticationManager“, о котором я не упоминал ранее.


Разглядим эти пункты по порядку.

1. Конфигурация политики доступа


Возвращаясь к постановке задачи, а также рассматривая теснее рассмотренный формат политики доступа, испробуем составить конфигурацию для нашего API. Получится дальнейший комплект правил:

   <policy resource="Users" action="GetAllUsers">
     <and>
       <claim claimType="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/system" claimValue="WebApplication" />
       <claim claimType="http://schemas.microsoft.com/ws/2008/06/identity/claims/role" claimValue="Admin" />
     </and>
   </policy>
   <policy resource="Users" action="Post">
       <claim claimType="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/system" claimValue="WPhoneApplication" />
   </policy>
   <policy resource="Users" action="RestorePassword">
     <claim claimType="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/system" claimValue="WPhoneApplication" />
   </policy>
   <policy resource="Users" action="GetUserById">
     <or>
       <and>
         <claim claimType="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/system" claimValue="WebApplication" />
         <claim claimType="http://schemas.microsoft.com/ws/2008/06/identity/claims/role" claimValue="Admin" />
       </and>
       <and>
         <claim claimType="http://schemas.microsoft.com/ws/2008/06/identity/claims/role" claimValue="User" />
         <!-- Как указать взамен {0} идентификатор пользователя тот, что отправил "request" ? -->
         <!-- <claim claimType="UserId" claimValue="{0}" /> -->
       </and>
     </or>
   </policy>
   <policy resource="Users" action="Put">
     <or>
       <and>
         <claim claimType="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/system" claimValue="WebApplication" />
         <claim claimType="http://schemas.microsoft.com/ws/2008/06/identity/claims/role" claimValue="Admin" />
       </and>
       <and>
         <claim claimType="http://schemas.microsoft.com/ws/2008/06/identity/claims/role" claimValue="User" />
         <!-- Как указать взамен {0} идентификатор пользователя тот, что отправил "request" ? -->
         <!-- <claim claimType="UserId" claimValue="{0}" /> -->
       </and>
     </or>
   </policy>


Видим, что некоторые policy-сегменты проще, некоторые труднее, некоторые повторяются. Разглядим по частям, начиная с простого варианта — политика доступа для приобретения списка пользователей:

   <policy resource="Users" action="GetAllUsers">
     <and>
       <claim claimType="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/system" claimValue="WebApplication" />
       <claim claimType="http://schemas.microsoft.com/ws/2008/06/identity/claims/role" claimValue="Admin" />
     </and>
   </policy>


Все предельно видимо: доступ к данному источнику есть у тех пользователей, комплект «claims» которых содержит оба «сlaim» — элемента.

Сейчас больше трудный вариант — приобретение информации о пользовю, и если она удачна — наполнить комплект «claims» пользователя.

Для аутентификации будем применять Basic Authentication и, скажем, ее реализацию вThinktecture.IdentityModel.45. Для этого в NuGet-консоли исполним команду:

Install-Package Thinktecture.IdentityModel


Код класса WebApiConfig изменим, Дабы он был примерно дальнейшим:

   public static class WebApiConfig
   {
       public static void Register(HttpConfiguration config)
       {
           var authentication = CreateAuthenticationConfiguration();
           config.MessageHandlers.Add(new AuthenticationHandler(authentication));

           config.MapHttpAttributeRoutes();
           config.Routes.MapHttpRoute(
               name: "DefaultApi",
               routeTemplate: "api/{controller}/{id}",
               defaults: new { id = RouteParameter.Optional }
           );

           config.EnableSystemDiagnosticsTracing();

           config.Filters.Add(new ClaimsAuthorizeAttribute());
       }

       private static AuthenticationConfiguration CreateAuthenticationConfiguration()
       {
           var authentication = new AuthenticationConfiguration
           {
               ClaimsAuthenticationManager = new AuthenticationManager(),
               RequireSsl = false //only for testing
           };

           #region Basic Authentication
           authentication.AddBasicAuthentication((username, password) =>
               {
                   var webSecurityService = ServiceLocator.Current.GetInstance<IWebSecurityService>();
                   return webSecurityService.Login(username, password);
               });
           #endregion

           return authentication;
       }
   }


Тут подмечу только то, что для проверки credentials пришедших из запроса у меня применяется некоторыйIWebSecurityService. Вы можете применять тут свою логику, скажем: return username == password;

Сейчас при всяком запросе к любому источнику будет производиться проверка аутентификации, но еще нам необходимо трансформировать базовый комплект «claims» нынешнего пользователя. Этим занимаетсяClaimsAuthenticationManager, а вернее наш преемник этого класса, тот, что мы теснее зарегистрировали:

   public class AuthenticationManager : ClaimsAuthenticationManager
   {
       public override ClaimsPrincipal Authenticate(string resourceName, ClaimsPrincipal incomingPrincipal)
       {
           if (!incomingPrincipal.Identity.IsAuthenticated)
           {
               return base.Authenticate(resourceName, incomingPrincipal);
           }

           var claimsService = ServiceLocator.Current.GetInstance<IUsersClaimsService>();
           var claims = claimsService.GetUserClaims(incomingPrincipal.Identity.Name);
           foreach (var userClaim in claims)
           {
               incomingPrincipal.Identities.First().AddClaim(new Claim(userClaim.Type, userClaim.Value));
           }
           return incomingPrincipal;
       }
   }


Как видим, если пользователь прошел аутентификацию — происходит приобретение его комплекта «claims», скажем из БД, посредством применения опять сделанного экземпляра IUsersClaimsService. Позже «трансформации» экземпляр ClaimsPrincipal возвращается дальше в конвеер для дальнейшего применения, скажем, авторизацией.

Проверка итога


Пришло время проверить работоспособность нашего решения. Для этого нам безусловно потребуются пользователи с теми либо иными «claims». Не будем длинно фантазировать над тем откуда их взять и немножко видоизменим AuthenticationManager в целях тестирования. Взамен примененияIUsersClaimsService вставим дальнейший код:

   public class AuthenticationManager : ClaimsAuthenticationManager
   {
       public override ClaimsPrincipal Authenticate(string resourceName, ClaimsPrincipal incomingPrincipal)
       {
           ...
           if (incomingPrincipal.Identity.Name.ToLower().Contains("user"))
           {
               incomingPrincipal.Identities.First().AddClaim(new Claim(ClaimTypes.Role, "User"));
           }
           return incomingPrincipal;
       }
   }


Отменно, сейчас все пользователи, логин которых содержит слово «user» будут содержать необходимый «claim».
Запустим план и перейдем по ссылке localhost:[port]/api/users


Вводим сокровенные логин и пароль, наша бесхитростная авторизация проверить их на равенство, а администратор авторизации превращает комплект «claims»:


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


Сейчас давайте припомним о том, что на этапе конфигурирования политики доступа нам пришлось на некоторое время позволить каждому пользователям просматривать информацию друг о друге, этим и воспользуемся. Испробуем узнать о пользователе с Id=100, зайдя по ссылке ~/api/users/100:

И вот мы отслеживаем, что некая реализация, появившаяся в кулуарах, возвращает информацию о любом пользователе :)

Завершение


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

Спасибо за внимание.


Источник: programmingmaster.ru

Оставить комментарий
БАЗА ЗНАНИЙ
СЛУЧАЙНАЯ СТАТЬЯ
СЛУЧАЙНЫЙ БЛОГ
СЛУЧАЙНЫЙ МОД
СЛУЧАЙНЫЙ СКИН
НОВЫЕ МОДЫ
НОВЫЕ СКИНЫ
НАКОПЛЕННЫЙ ОПЫТ
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB