Публикация в Azure Blob Storage с помощью Shared Access Signature

в 11:58, , рубрики: .net, blob, mobile services, Windows 8, windows azure, WinRT, метки: , , , ,

Привет. Эта статья во многом будет похожа на предыдущую. Здесь я также расскажу, как с помощью Windows Azure Mobile Services публиковать данные большого объема в хранилище Blob Storage. Однако на этот раз мы избавимся от WCF сервиса и заодно добавим дополнительный слой защиты от несанкционированного доступа с помощью Shared Access Signature. Цель этой статьи — показать дополнительные возможности Windows Azure для работы с данными, а также возможности по быстрому созданию серверного бекенда Mobile Services.

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

Shared Access Signature

Для начала стоит рассказать, а что же такое этот самый Shared Access Signature, или просто SAS. За этими тремя буквами скрывается не что иное, как простой URL, позволяющий разграничить право доступа к ресурсу Windows Azure Blob, Table или Queue. Если вспомнить предыдущую статью и то, как осуществлялся обмен данными между приложением Windows 8 и сервисом WCF, то сразу можно понять, что такое решение явно страдало от отсутствия какой-либо защиты. Во-первых, канал передачи данных был открыт (HTTP), что позволяло потенциальному злоумышленнику перехватить пользовательскую информацию. Во-вторых, даже если канал сделать зашифрованным (HTTPS), то у нас не было никакой проверки на подлинность запроса. Я хочу сказать, что будь у нас в системе несколько пользователей, то не было никакой возможности определить, имеет ли конкретный юзер права на запись в хранилище, да и вообще — из приложения ли отправлен запрос или это просто хакер пытается отправляет свои вредоносные данные. Ну а вариант с хранением данных для подключения к Blob Storage прямо в приложении Windows 8 я вообще не рассматриваю — уж слишком не секьюрно.

Использование Shared Access Signature в сочетании с Mobile Services позволит нам избежать подобных проблем. Все дело в том, что SAS будет формироваться на серверной стороне, непосредственно в Mobile Services, где мы защищены от посторонних глаз, а также имеем всю необходимую информацию о пользователе и можем ограничить доступ анонимным людям. Сама же подпись будет выдаваться на очень ограниченный период времени, минут на 5, что также сведет к минимуму возможность ее хищения и использования в корыстных целях.

Пожалуй словами все довольно сложно объяснить, поэтому я предлагаю перейти к практике. Для этого за основу я возьму идею из предыдущего поста и немного видоизменю код приложения.

Подготовка

Итак, для начала нам надо создать необходимые сервисы в Windows Azure. Переходим в новый портал управления и создадим Blob-хранилище. Через стандартное меню добавления сервиса выбираем пункт Data Services -> Storage -> Quick Create, где нам нужно указать имя будущего сервиса, регион  подписку, в рамках которой он должен будет работать:

Создание Windows Azure Blob Storage

Также нам потребуется мобильная слуба, создать которую можно в том же меню портала в разделе Compute -> Mobile Service:

Создание Windows Azure Mobile Service

Оба эти шага вы можете не делать, если у вас уже есть созданные blob-хранилище или мобильная служба.

Создание Shared Access Signature

В отличие от того, с чего мы начинали в прошлый раз, сейчас нашим первым местом для написания кода станет Windows Azure Mobile Services. Да, я тут нигде не описался — мы будем писать код прямо в облачном сервисе. Такое возможно благодаря встроенной функции скриптов, которые могут выполняться при совершении одной из CRUD операций над таблицей.

В этот раз мы будем в нашем приложении сохранять фотографии с кратким их описанием. Для начала создадим новую таблицу в Mobile Services, в которой будут располагаться записи. Назовем ее Picture:

Создание Data Table

При создании таблицы в полях с названием Permission укажите «Anybody with the application key». Это значит, что любой пользователь (даже анонимный), который имеет в своем распоряжении ключи доступа к Mobile Service, может осуществлять данную операцию. Если же выбрать значение «Only authenticated users», то перед доступом к операции будет проверяться пользователь, и, если он не задан, операция будет запрещена к выполнению. В этой статье я не буду рассматривать аутентификацию с помощью Mobile Services, поэтому оставим в этих полях значения по умолчанию.

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

Скрипт Data Table

Если развернуть меню Operation, то в нем будет четыре опции: Insert, Update, Delete и Read. Каждой из этих опций соответствует отдельный скрипт, который выполняется в момент наступления того или иного события. Если проводить аналогию, то эти скрипты можно сопоставить с триггерами базы данных. При этом главным телом самого «тригера» является одноименная функция, но можно писать дополнительные функции ниже. Язык этих скриптов — JavaScript, со всеми положительными вытекающими последствиями, в виде асинхронности, замыканий и т.п.

Итак, когда с принципом работы скриптов стало немного понятнее, настало самое написать немного серверного кода, который будет создавать для нас Shared Access Signature. Для этого нам нужен скрипт на событие вставки — Insert. Скрипты в Mobile Services позволяют пользоваться некоторыми API для работы с другими сервисами Windows Azure. Такие API подключаются как внешние библитечные классы и используются в коде легко и просто. Давайте разберем это на примере (приношу извинения за то, что код вставляется как картинка, но так нагляднее):

Скрипт создания Shared Access Signature 1

Строки 1 и 2 как раз объявляют подключаемые модули и дают им названия. Модуль azure отвечает за работу с сервисами Windows Azure, а модуль querystring — за формирование URL с параметрами (увидим ниже). Однако прежде чем мы перейдем к рассмотрению кода (который, кстати, должен быть весьма понятен), я хочу обратить ваше внимание на то, насколько удобен встроенный редактор. Во-первых, тут есть подсветка синтаксиса и автоматическое выравнивание, чем, впрочем, уже никого не удивишь. Во-вторых, в нем встроена автоматическая валиация кода. Фунцкии createAccessPolicy и createResourceURLWithSAS еще не объявлены в коде, и редактор выделяет их подчеркиванием и красным штрихом на полях. Ну и в дополнение, в-третьих, в редактор встроен IntelliSense. Он, конечно, не так крут, как в Visual Studio, но тоже слегка упрощает жизнь.

Теперь посмотрим на сам код. Как я уже говорил, тут нет ничего сложного и непонятного. Если в двух словах, то нам вместе с записью информации о новом изображении  надо также записать URL, с помощью которого мы сможем осуществлять операции с BLOB-хранилищем (этот URL содержит SAS). Для начала надо получить доступ к хранилищу с помощью его имени и ключа (строка 14), которые можно получить в портале управления Windows Azure в разделе управления хранилищами. Затем мы создаем контейнер (строка 15), если его еще нет, и задаем права доступа: разрешаем чтение всем. Затем, с помощью анонимной функции и замыканий мы определяем код, который будет выполнятся после того, как создастся контейнер. Так мы добиваемся асинхронной работы приложения, что лучше для производительности сервера. С помощью не описанной пока функции createAccessPolicy (строка 20) мы создаем политику доступа (что мы можем и когда) и формируем URL с помощью createResourceURLWithSAS (строка 23).

Давайте рассмотрим остальной код:

Скрипт создания Shared Access Signature 2

В этом отрывке кода размещаются остальные функции, которых не хватало в предыдущем блоке. Наиболее интересными являются createAccessPolicy и getSAS. Первая просто возвращает специальный объект, описывающий политику доступа. Мы создаем его с правами только на запись, а также указываем время истечения прав — 5 минут с момента создания. Явно задавать время жизни SAS — довольно правильный подход. Если вдруг злоумышленник каким-то образом получит ссылку с SAS, у которой установлен параметр Expiry, то он просто ничего не сможет с ней сделать, поскольку по истечении срока доступ закрыватся и такой SAS становится бесполезным.

Функция getSAS содержит в себе довольно простую логику создания непосредственно объекта SAS и формирования части URL строки с параметрами. Все это делается с помощью API для Windows Azure, подключенным к текущему скрипту в самом начале.

По окончании редактирования скрипта не забываем нажимать на кнопку Save внизу страницы, иначе все труды на смарку.

Клиент

Итак, мы разобрались с серверной логикой и поняли, что особо ничего сложного в ней нет. У нас теперь есть некая URL, в которой прописан Shared Access Signature, и теперь давайте посмотрим, что с ней делать. В предыдущей статье приложение для Windows 8 отвечало лишь за общение с Windows Azure Mobile Services и WCF сервисом, перекладывая ответственность по сохранению бинарных данных с блобах на другие подсистемы. В этот же раз приложение будет самостоятельно сохранять всю информацию в сервисы Windows Azure.

Для начала давайте создадим пустое приложение для Windows 8. Я не стал ничего выбирать в качестве шаблона, остановившись на Blank App. Поскольку сейчас статья не о том, как писать приложения, интерфейс и функциональность программы будут на минимально достаточном уровне. Иными словами — без изысков, но работает.

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

Интерфейс программы для Windows 8

Надо немного покаяться и извиниться перед всеми дизайнерами и людьми с хорошим чувством вкуса — приложение сделано не ради пользования, а для демонстрации. Соответственно и код тоже будет без особых изысков — ни MVVM, ни прочих крутых штук. Три метода, зато все наглядно.

Начнем, пожалуй, с того, что определим класс, который будет соответствовать таблице Picture в Mobile Services:

    public class Picture
    {
        public int Id { get; set; }

        [DataMember(Name = "description")]
        public string Description { get; set; }

        [DataMember(Name = "fileName")]
        public string FileName { get; set; }

        [DataMember(Name = "imageurl")]
        public string ImageUrl { get; set; }
    }

Это простой класс, в котором обязательно должно быть поле/свойство Id. Остальные свойства преобразуются в аналогичные поля таблицы. Атрибут DataMember помогает явно указать имя поля таблицы, в которое должен превратиться то или иное свойство класса.

Далее метод, отвечающий за получение изображения с камеры и вывод его на экран:

        private async void ButtonPhotoClick(object sender, RoutedEventArgs e)
        {
            // Получение фото или видео
            CameraCaptureUI cameraCapture = new CameraCaptureUI();
            _file = await cameraCapture.CaptureFileAsync(CameraCaptureUIMode.PhotoOrVideo);

            BitmapImage bitmapImage = new BitmapImage(); // Сохранение сначала в битмап, а потом на форму
            FileRandomAccessStream stream = (FileRandomAccessStream)await _file.OpenAsync(FileAccessMode.Read);
            bitmapImage.SetSource(stream);
            Photo.Source = bitmapImage;
        }

Тут тоже все просто. Кто знаком с разработкой для Windows 8 (WinRT), тот сразу поймет, что тут происходит. А кто не знаком — тот догадается. Сначала мы просим от камеры фотографию или видео (кстати видео тоже без проблем можно передать в BLOB-хранилище), а затем из полученного файла получаем поток, пишем его в битмап и выводим на форму приложения. Да, и не забываем, что для работы с камерой в Windows 8 надо, во-первых, прописать в манифесте приложения, что мы вообще в принципе собираемся это делать, а во-вторых, получить от пользователя разрешение на доступ к устройству. Если какое-то из этих условий не выполняется, то код выше упадет с ошибкой, потому что в _file не зпишется ничего.

Ну и на закуску осталось самое главное — запись полученных данных в таблицу Mobile Service, получение от нее Shared Access Control и запись файла в облачное хранилище. Звучит сложно, а на самом деле — несколько строк кода:

        private async void ButtonSaveClick(object sender, RoutedEventArgs e)
        {
            var picture = new Picture();
            picture.Description = Description.Text;
            picture.FileName = _file.Name;

            // Созраняем запись в таблице Mobile Service
            var table = App.MobileService.GetTable<Picture>();
            await table.InsertAsync(picture);

            var containerName = "mypictures";

            // Загружаем картинк на сервер            
            using (var fileStream = await _file.OpenStreamForReadAsync())
            {
                await UploadBlob(fileStream, picture.FileName, picture.ImageUrl, containerName);
            }

            // Удалим SAS из URL изображения
            picture.ImageUrl = picture.ImageUrl.Substring(0, picture.ImageUrl.IndexOf('?'));
            await table.UpdateAsync(picture); 
        }

        public static async Task UploadBlob(Stream fileStream, string fileName, string blobUrl, string containerName)
        {
            fileStream.Position = 0;
            var sasUri = new Uri(blobUrl);

            // Получаем SAS из URL картинки
            StorageCredentials cred = new StorageCredentials(sasUri.Query.Substring(1));
            // Получаем контейнер
            CloudBlobContainer container = new CloudBlobContainer(new Uri(string.Format("https://{0}/{1}", sasUri.Host, containerName)), cred);
            // И соответствующий BLOB
            CloudBlockBlob blobFromSASCredential = container.GetBlockBlobReference(fileName);
            // Пишем данные
            await blobFromSASCredential.UploadFromStreamAsync(fileStream.AsInputStream());
        }

Пойдем по порядку. Метод ButtonSaveClick вызывается в момент нажатия на кнопку Save. В нем мы сначала формируем объект Picture, заполняя его значениеми из поля описания и именем файла изображения. Затем начинается работа с Windows Azure. Нам надо получить таблицу Picture из Mobile Services и вставить в нее новую запись. Как видите, делается это двумя строками. Затем происходит работа с BLOB хранилищем. Сразу после того, как новая запись вставилась в таблицу, у объекта picture изменилось свойство ImageUrl, в который записался URL будущего блоба с добавленным к нему Shared Access Signature. С помощью этого URL мы теперь можем обращаться к Blob Storage, не зная его имени и Primary Key.

Работа с хранилищем вынесена в отдельный, но также небольшой метод UploadBlob. Все необходимые действия вписываются в четыре простых шага. Сначала мы получаем Shared Access Signature из URL картинки, который передан в виде query-параметра. Затем с помощью этого SAS мы получаем контейнер, с которым будет работать. Затем аналогично получаем блоб по его имени, ну и под конец — загружаем бинарные данные изображения из потока прямиком в Azure Blob Storage.

Да, я забыл упомянуть, что для работы с Blob Storage в Windows 8 необходима библиотека, которыю можно взять тут. В NuGet я не нашел подобного, поэтому подключать надо вручную. Но думаю, что в скором времени это изменится.

Заключение

В этой статье я попытался наглядно показать, как можно работать с данными в Windows Azure из мобильных приложений Windows 8 более легко, надежно и ез дополнительных инфраструктурных слоев с помощью Windows Azure Mobile Services и Shared Access Signature. По большому счету, вся логика по записи/извлечении данных из табличного или блоб-хранилища умещается в пару трок кода, что существенно упрощает жизнь, когда надо получить результат своей работы как можно быстрее. Для независимых разработчиков и стартапов такая возможность очень сильно поможет в успешном старте и избавит от лишней головной боли.

P.S. Эта статья частично была подготовлена на основе официальных примеров исходного кода по работе с Mobile Services и Shared Access Signature. Ознакомиться самим с этим примером можно тут: http://code.msdn.microsoft.com/windowsapps/Upload-File-to-Windows-c9169190

Автор: glamcoder

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js