habrahabr

Локальные учётные записи в Microsoft Azure Mobile Services

  • пятница, 2 января 2015 г. в 02:10:39
http://habrahabr.ru/post/247199/

Ещё одна недостаточно описанная тема про Microsoft Azure Mobile Services — авторизация, которая к тому ж налажена лишь в последних версиях. Разумеется, с самых ранних версий было перечисление MobileServiceAuthenticationProvider, позволявшее простым способом выполнить авторизацию одним из заданных методов. Но вряд ли этот набор — всегда самое удобное решение для пользователей. Тут возможно два направления расширения — добавление новых вариантов механизма OpenId или свой механизм авторизации. Далее будет рассмотрен второй вариант.

Начнём с того, что если для создания проекта использовать слишком старый шаблон из Visual Studio (например 2-е обновление для 2013-го), то в нём версия NuGet для WindowsAzure.MobileServices.Backend может не поддерживать рассмотренное далее решение. На период написания статьи с последней версией 1.0.439 были сложности совместимости с новыми версиями других модулей, поэтому здесь используется следующий набор:
Install-Package AutoMapper -Version 3.2.1
Install-Package System.IdentityModel.Tokens.Jwt -Version 3.0.2
Install-Package Microsoft.Owin.Security -Version 2.1.0
Install-Package Microsoft.Owin.Security.OAuth -Version 2.1.0
Install-Package Microsoft.Owin.Security.Cookies -Version 2.1.0
Install-Package Microsoft.Owin.Security.Jwt -Version 2.1.0
Install-Package Microsoft.Owin.Security.ActiveDirectory -Version 2.1.0
Install-Package Microsoft.Owin.Security.Facebook -Version 2.1.0
Install-Package Microsoft.Owin.Security.Google -Version 2.1.0
Install-Package Microsoft.Owin.Security.Twitter -Version 2.1.0
Install-Package Microsoft.Owin.Security.MicrosoftAccount -Version 2.1.0
Install-Package WindowsAzure.MobileServices.Backend.Entity -Version 1.0.405

Сперва создадим классы DemoCredentials (наследованный от ProviderCredentials) и LocalLoginProvider (наследованный от LoginProvider) и объявим их следующим образом:
    public class DemoCredentials: ProviderCredentials
    {
        public string AccessToken { get; set; }

        public DemoCredentials()
            : base(DemoLoginProvider.ProviderName)
        {

        }
    }

    public class DemoLoginProvider: LoginProvider
    {
        public const string ProviderName = "Demo";

        public override string Name
        {
            get { return ProviderName; }
        }

        public DemoLoginProvider(IServiceTokenHandler tokenHandler)
            : base(tokenHandler)
        {

        }

        public override void ConfigureMiddleware(Owin.IAppBuilder appBuilder, Microsoft.WindowsAzure.Mobile.Service.ServiceSettingsDictionary settings)
        {
            return;
        }

        public override ProviderCredentials CreateCredentials(System.Security.Claims.ClaimsIdentity claimsIdentity)
        {
            System.Security.Claims.Claim name = claimsIdentity.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier);
            System.Security.Claims.Claim providerAccessToken = claimsIdentity.FindFirst(ServiceClaimTypes.ProviderAccessToken);
            DemoCredentials credentials = new DemoCredentials
            {
                UserId = this.TokenHandler.CreateUserId(Name, name != null ? name.Value : null),
                AccessToken = providerAccessToken != null ? providerAccessToken.Value : null
            };
            return credentials;
        }

        public override ProviderCredentials ParseCredentials(Newtonsoft.Json.Linq.JObject serialized)
        {
            return serialized.ToObject<DemoCredentials>();
        }
    }

Метод ConfigureMiddleware в данном случае не нужен, поскольку он предназначен для внешней авторизации.

Далее создадим API-контроллер и объявим в нём основной метод авторизации, который будет возвращать тип данных LoginResult. Для упрощения примера в нём не будет проверки параметров авторизации, полагаю, читателям не составит сложностей добавить необходимый для конкретной задачи способ их проверки.
        [HttpPost]
        [Route("api/Authentication/Authenticate")]
        [AllowAnonymous]
        public async Task<HttpResponseMessage> Authenticate()
        {
            DemoLoginProvider provider = new DemoLoginProvider(Handler);
            System.Security.Claims.ClaimsIdentity claimsIdentity = new System.Security.Claims.ClaimsIdentity();
            claimsIdentity.AddClaim(new System.Security.Claims.Claim(System.Security.Claims.ClaimTypes.NameIdentifier, "fakeUser"));
            LoginResult loginResult = provider.CreateLoginResult(claimsIdentity, Services.Settings.MasterKey);
            return Request.CreateResponse(HttpStatusCode.OK, loginResult);
        }

Класс LoginResult содержит очень важный параметр — AuthenticationToken, который понадобится на клиентской стороне. Он выдаётся сервером на заданное в параметрах время и определяет сам факт наличия авторизации от имени конкретного пользователя — в данном случае «Demo:fakeUser».

Свойство Handler объявляется в контроллере следующим образом:
        public IServiceTokenHandler Handler { get; set; }

После этого Autofac будет выставлять его требуемое значение.

Далее рассмотрим пример клиентского приложения на основе Xamarin.Android. Можно использовать одну сборку, но в целях демонстрации межплатформенных возможностей будет использоваться PCL с профилем 111. Этот профиль поддерживается в Xamarin.Android, Xamarin.iOS и WinStore-приложениях. Для упрощения примера будет использовано сокращённое количество слоёв приложения, а при реализации пользовательского интерфейса будет исключена обработка жизненного цикла activity. Выполним команду:
Install-Package WindowsAzure.MobileServices

Затем реализуем метод авторизации:
        public async Task AuthenticateAsync()
        {
            LoginResultDTO loginResult = await _channel.InvokeApiAsync<LoginResultDTO>("Authentication/Authenticate").ConfigureAwait(false);
            MobileServiceUser user = new MobileServiceUser(loginResult.User.UserId)
            {
                MobileServiceAuthenticationToken = loginResult.AuthenticationToken
            };
            _channel.CurrentUser = user;
        }


После этого при вызове любого метода контроллера, требующего авторизации, в свойстве User контроллера будет содержаться указанное значение «Demo:fakeUser». Остальные части клиентского приложения содержат распространённые в других приложениях строчки кода, для более детального изучения архив исходников примера можно скачать здесь.