В предыдущих статьях ( 1 , 2 , 3 ) я рассмотрел создание одностраничного приложения на основе Ember.js, а затем подключил его к Windows Azure Mobile Services для хранения данных. Но в настоящее время это позволяет каждому публиковать анонимно. По крайней мере, мне нужна базовая аутентификация, чтобы я мог убедиться, что пользователь — реальный человек, и, возможно, привлечь его к ответственности за то, что он публикует.
Ограничение разрешений
Я хочу, чтобы только авторизованные пользователи могли писать сообщения, но я хочу, чтобы все могли их читать. Это можно сделать довольно легко с помощью портала управления Windows Azure , выполнив следующие действия:
-
Откройте браузер и перейдите к своему экземпляру Mobile Service, выберите « Данные» , таблицу сообщений и, наконец, выберите « Разрешения» .
-
Установите разрешения на вставку , обновление и удаление только для прошедших проверку пользователей . Оставьте прочтение в любом месте с ключом приложения .
-
Нажмите кнопку Сохранить внизу, чтобы применить изменения.
На этом этапе, если вы попытаетесь создать новое сообщение с помощью приложения, оно молча завершится неудачей.
Настройка аутентификации
Службы Windows Azure Mobile Services упрощают настройку проверки подлинности с использованием Facebook, Google, учетной записи Microsoft (ранее LiveID) или Twitter в качестве поставщика проверки подлинности. Документация на WindowsAzure.com довольно обширна, чтобы уже настроить это, поэтому на этом шаге я просто следовал « Зарегистрировать ваше приложение для аутентификации» и настроить раздел « Мобильные сервисы » документа. Для моих провайдеров я просто использовал Google и Twitter.
Если вы следуете дальше, просто сделайте этот раздел и остановитесь, когда вы нажмете на «Ограничить разрешения для аутентифицированных пользователей», поскольку это уже было сделано на предыдущем шаге.
Реализация входа / выхода
Поскольку вход в систему / выход из системы глобален, я хотел, чтобы ссылка была очень заметной. Так что ставить его в конце панели навигации было естественным выбором.
Мне также нужен был способ глобального отслеживания состояния входа в систему, поэтому я использовал Ember.StateManager для отслеживания этого состояния.
Тогда есть действия для обработки входа и выхода. Они должны работать независимо от страницы, которую я просматриваю; Я не хочу реализовывать эти действия на всех контроллерах. К счастью, действия всплывают в ApplicationController, если они не обрабатываются другими контроллерами.
высказывать
Спасибо ветке StackOverflow за указание на решение этой проблемы. Вот LoginStateManager
//login statemachine App.LoginStateManager = Ember.StateManager.create({ initialState: "isNotAuthenticated", isAuthenticated: Ember.State.create({ enter: function () { console.log("enter " + this.name); }, logout: function (manager, context) { manager.transitionTo('isNotAuthenticated'); } }), isNotAuthenticated: Ember.State.create({ enter: function () { console.log("enter " + this.name); }, login: function (manager, credentials) { console.log(credentials); manager.transitionTo('isAuthenticated'); } }) });
Состояние либо isNotAuthenticated (по умолчанию), либо isAuthenticated. Он будет входить в консоль при изменении состояния. Обратите внимание manager.transitionTo()
. Это переход между состояниями.
Контроллер (ы)
Мне нужно было определить ApplicationController, так как именно там я и хотел обрабатывать события входа / выхода. Это также должно сказать конечному автомату об изменении состояния, а также предоставить свойство isAuthenticated, которое панель навигации может использовать, чтобы определить, показывать ли вход / выход из системы. Я также хотел использовать это в NewpostController, поэтому я также добавил его туда. И я хочу получить имя пользователя автоматически из токена аутентификации, поэтому я просто удалил поле автора здесь и в шаблоне ниже. Вот код:
//Application controller, since we want login to be an application wide thing //login/logout events are bubbled up to here regardless of the page you are on App.ApplicationController = Em.Controller.extend({ authStateBinding: Ember.Binding.oneWay('App.LoginStateManager.currentState.name'), authState: null, isAuthenticated: function () { return (this.get('authState') == 'isAuthenticated'); }.property('authState'), login: function(provider) { client.login(provider).then(function(results) { App.LoginStateManager.send("login", client.currentUser); }); }, logout: function() { client.logout(); App.LoginStateManager.send("logout"); } });
// Newpost controller App.NewpostController = Ember.ObjectController.extend({ title: '', body: '', authStateBinding: Ember.Binding.oneWay('App.LoginStateManager.currentState.name'), authState: null, isAuthenticated: function () { return (this.get('authState') == 'isAuthenticated'); }.property('authState'), save: function() { //create the post var now = new Date(); var post=App.Post.create({ title: this.get('title'), body: this.get('body'), posted: now.toString('dddd, MMMM, yyyy'), }); post.save(); // set these back to '' so the form is pretty this.set('title',''); this.set('body',''); //transition back to posts this.transitionToRoute('posts'); } });
Обратите внимание, что isAuthenticated
просто проверяет authState объекта LoginStateManager для определения состояния. Но чтобы добраться до этой точки, вы должны подключить привязку, которая возвращает текущее состояние.
Для событий входа в систему я передаю провайдера из пользовательского выбора на панели навигации в качестве provider
параметра, а затем использую его, client.login()
чтобы сообщить Windows Azure Mobile Services, какого из сконфигурированных провайдеров использовать для этого запроса аутентификации.
Для перехода между состоянием входа / выхода из системы он просто отправляет вход / выход из системы менеджеру состояния вместе с информацией currentUser, возвращаемой client.login()
запросом на вход в систему.
Navbar
Ниже приведена версия файла application.hbs, обновленная для обеспечения функциональности входа / выхода из системы:
<div class="navbar navbar-inverse"> <div class="navbar-inner"> <div class="container"> <a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse"> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </a> {{#linkTo index class="brand"}}My Great Blog{{/linkTo}} <div class="nav-collapse collapse"> <ul class="nav"> <li>{{#linkTo posts}}<i class="icon-book icon-white"></i> Posts{{/linkTo}}</li> <li>{{#linkTo newpost}}<i class="icon-pencil icon-white"></i> Write{{/linkTo}}</li> {{#if isAuthenticated}} <li> <a {{action logout}} href="#"><i class="icon-remove icon-white"></i> Logout</a> </li> {{else}} <li class="dropdown"> <a id="loginproviders" href="#" role="button" class="dropdown-toggle" data-toggle="dropdown"> <i class="icon-user icon-white"></i> Login <b class="caret"></b> </a> <ul class="dropdown-menu" role="menu" aria-labelledby="loginproviders"> <li role="presentation"><a {{action login "twitter"}} href="#">Twitter</a></li> <li role="presentation"><a {{action login "google"}} href="#">Google</a></li> </ul> </li> {{/if}} </ul> </div> </div> </div> </div> {{outlet}}
В {{#if isAuthenticated}} отображает вход или выход из системы меню выбора по мере необходимости.
Для пункта меню входа в систему я выбрал выпадающий список между Twitter и Google. Важный бит здесь является {{Действие Войти «твиттер»}} и {{Действие Войти «Google»}} . Оба вызывают действие входа и передают ему строку «twitter» или «google». Это будет отображаться в контроллере как provider
параметр, который обсуждался ранее, если вы используете другого провайдера, просто измените соответствующие поля.
Для выхода из системы это просто вызывает действие выхода из системы.
Улучшения после записи
Ранее я упоминал, что после ограничения доступа к функциям CRUD в мобильных службах Windows Azure, если вы отправите новое сообщение, выйдя из системы, произойдет молчание. Кто на самом деле хочет заполнить огромный пост в блоге, нажать «Отправить» и увидеть все свои работы потерянными? Почти никто. Поэтому, чтобы сделать это лучше, я добавил проверку isAuthenticated и отображал предупреждение, если вы не вошли в систему.
<div class="navbar navbar-inverse"> <div class="navbar-inner"> <div class="container"> <a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse"> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </a> {{#linkTo index class="brand"}}My Great Blog{{/linkTo}} <div class="nav-collapse collapse"> <ul class="nav"> <li>{{#linkTo posts}}<i class="icon-book icon-white"></i> Posts{{/linkTo}}</li> <li>{{#linkTo newpost}}<i class="icon-pencil icon-white"></i> Write{{/linkTo}}</li> {{#if isAuthenticated}} <li> <a {{action logout}} href="#"><i class="icon-remove icon-white"></i> Logout</a> </li> {{else}} <li class="dropdown"> <a id="loginproviders" href="#" role="button" class="dropdown-toggle" data-toggle="dropdown"> <i class="icon-user icon-white"></i> Login <b class="caret"></b> </a> <ul class="dropdown-menu" role="menu" aria-labelledby="loginproviders"> <li role="presentation"><a {{action login "twitter"}} href="#">Twitter</a></li> <li role="presentation"><a {{action login "google"}} href="#">Google</a></li> </ul> </li> {{/if}} </ul> </div> </div> </div> </div> {{outlet}}
Я также удалил поле автора, так как установил его в аутентификации, возвращаемой из мобильных сервисов.
Получение имени пользователя
Mobile Services предоставляет серверный JavaScript (node.js) для операций CRUD. Я использую операцию вставки, чтобы вытащить имя пользователя и добавить его в сообщение, поскольку оно сохраняется в хранилище данных. Вот шаги, чтобы сделать это.
-
Используя портал управления Windows Azure, перейдите к своему экземпляру Mobile Services и выберите таблицу сообщений .
-
Выберите « Сценарий» и, наконец, выберите « Вставить» в раскрывающемся списке « Операция» .
-
Замените существующий код следующим, затем нажмите кнопку Сохранить
function insert(item, user, request) { item.author = "Unknown"; var identities = user.getIdentities(); var url; if (identities.google) { var googleAccessToken = identities.google.accessToken; url = 'https://www.googleapis.com/oauth2/v1/userinfo?access_token=' + googleAccessToken; } else if (identities.facebook) { var fbAccessToken = identities.facebook.accessToken; url = 'https://graph.facebook.com/me?access_token=' + fbAccessToken; } else if (identities.microsoft) { var liveAccessToken = identities.microsoft.accessToken; url = 'https://apis.live.net/v5.0/me/?method=GET&access_token=' + liveAccessToken; } else if (identities.twitter) { var userId = user.userId; var twitterId = userId.substring(userId.indexOf(':') + 1); url = 'https://api.twitter.com/1/users/show.json?user_id=' + twitterId; } if (url) { var requestCallback = function (err, resp, body) { if (err || resp.statusCode !== 200) { console.error('Error sending data to the provider: ', err); request.respond(statusCodes.INTERNAL_SERVER_ERROR, body); } else { try { var userData = JSON.parse(body); item.author = userData.name; request.execute(); } catch (ex) { console.error('Error parsing response from the provider API: ', ex); request.respond(statusCodes.INTERNAL_SERVER_ERROR, ex); } } } var req = require('request'); var reqOptions = { uri: url, headers: { Accept: "application/json" } }; req(reqOptions, requestCallback); } else { // Insert with default user name request.execute(); } }
Это выполнит поиск понятного имени пользователя на основе недружественного идентификатора пользователя, возвращенного поставщиком аутентификации, а затем использует его для заполнения поля «Авторы».
Также большое спасибо Карлосу Фигейре и Девхаммеру за их блоги об этом.
Тестирование новых изменений
Если вы запустите приложение локально, используя grunt server
, вы заметите, что теперь есть меню входа в систему, и что выбор одного из элементов из этого предоставляет вам страницу аутентификации / входа для этого провайдера. Если вы перейдете по ссылке «Написать» во время выхода из системы, то заметите, что теперь вы получаете приятное сообщение о том, что вам необходимо войти в систему, чтобы написать сообщение.
Но есть небольшая проблема в этой точке; некоторым браузерам ( кашель IE кашляет ) не нравится тот факт, что вы запускаете страницу локально, но аутентификация происходит в Интернете. Так что, хотя у вас есть окно аутентификации, оно на самом деле не работает. Вы можете заставить его работать, отключив некоторые средства безопасности, чтобы проверить это локально. Для этого откройте вкладку « Безопасность » в окне «Свойства обозревателя», нажмите « Локальная интрасеть» , нажмите « Сайты» и отключите параметр « Автоматически определять сеть интрасети» . Не забудьте включить его после тестирования.
Или вы можете просто опубликовать это на веб-сайте Windows Azure и попробовать его таким образом.
Создать веб-сайт Windows Azure
Вы можете создать новый веб-сайт из консоли управления Windows Azure , выполнив следующие действия:
-
Нажмите + NEW , выберите Compute , Web Site и Quick Create . При появлении запроса укажите уникальный URL-адрес и выберите регион.
Если веб-сайт недоступен, вам нужно будет выполнить шаги, чтобы включить предварительный просмотр для вашей учетной записи.
-
Как только сайт будет создан, выберите его и перейдите на панель инструментов . Здесь вы увидите несколько быстрых ссылок для таких вещей, как URL сайта, сброс / создание учетных данных для развертывания и т. Д. Если вы еще не включили развертывание FTP или не задали учетные данные, вы можете сделать это, используя ссылки здесь.
Части, которые вы хотите, это имя хоста FTP и DEPLOYMENT / FTP USER . Сохраните это.
Настройка Grunt для развертывания FTP
Существует задача Grunt, которая будет развертывать скомпилированное приложение с использованием FTP. Вот шаги для настройки этого:
-
Из сеанса командной строки / bash / Terminal измените каталоги на каталог emberapp и выполните следующую команду:
npm install grunt-ftp-deploy --save-dev
Это установит биты задачи ftp-deploy и сохранит их в разделе devDependencies файла package.json .
-
Откройте файл gruntfile.js и добавьте в него следующее, а затем сохраните файл:
'ftp-deploy': { build: { auth: { host: "waws-prod-blah-001.ftp.azurewebsites.windows.net", port: 21, authKey: 'key1' }, src: 'dist', dest: '/site/wwwroot' } },
Замените хост на FTP-хост, возвращенный из Dashboard вашего веб-сайта. Например, если имя хоста FTP на панели мониторинга отображается как ftp://waws-prod-blah-001.ftp.azurewebsites.windows.net/, вы просто установите хост как waws-prod-blah-001. .ftp.azurewebsites.windows.net.
Я просто добавил это между
clean:
иjshint:
секции gruntfile, так что должно быть безопасным местом. -
Откройте файл .gitignore и добавьте в конце новую строку, содержащую следующее значение, а затем сохраните файл:
.ftppass
.Ftppass файл содержит имя пользователя / пароль для FTP — сервера. Добавление его в .gitignore не позволяет Git записать значение в хранилище.
-
Создайте новый файл с именем .ftppass в каталоге emberapp . Добавьте к нему следующее, заменив «website \ username» и «website website» на ваше имя пользователя и пароль.
{ "key1": { "username": "website\\username", "password": "website password" } }
Вы можете получить свое имя пользователя для FTP-сервера с панели инструментов . Обратите внимание, что в нем есть символ «\». Вам придется удвоить это при записи в файл .ftppass , так как один ‘\’ интерпретируется как escape-последовательность.
Сохраните файл.
Построить и развернуть
Для сборки и развертывания выполните следующие действия:
-
В командной строке \ bash \ Terminal session выполните следующее:
grunt build
Это будет вращаться некоторое время, а затем, в конце концов, скажет « Готово» без ошибок. Теперь у вас должна быть папка dist под emberapp . Задача сборки берет файлы для приложения, минимизирует JS, сжимает вещи и т. Д., И это то, что вы получаете.
-
Далее разверните:
grunt ftp-deploy
Это должно пройти через несколько операторов выгрузки файлов для каждого файла в папке dist и, наконец, дать вам ** Готово без ошибок. «
Авторизуйте свой веб-сайт для использования мобильных сервисов
По умолчанию мобильные сервисы разрешают запросы от localhost, но не где-либо еще. Поэтому нам нужно добавить ваш веб-сайт Windows Azure, чтобы он мог работать с мобильными службами. Для этого выполните следующие действия:
-
Перейдите на панель инструментов веб-сайта Windows Azure и найдите ссылку в разделе URL-адрес сайта . Возьмите имя хоста оттуда. Например, если URL-адрес сайта — http://myawesomesite.azurewebsites.net, то именем хоста будет просто myawesomesite.azurewebsites.net.
-
Перейдите к своему экземпляру Windows Azure Mobile Services и выберите « Настроить» . Прокрутите страницу вниз, пока не найдете раздел « Перекрестное распределение ресурсов» (CORS) . Введите здесь имя хоста для вашего сайта, а затем нажмите кнопку Сохранить внизу, чтобы сохранить изменения.
На этом этапе вы должны иметь возможность посещать URL сайта в браузере, и он должен позволять вам входить / выходить, просматривать и создавать сообщения.
Резюме
Этот пост закончился дольше, чем я ожидал, надеюсь, это был не TL; DR. И, надеюсь, вы узнали, что подключить мобильные сервисы к Ember.js довольно просто и что серверные скрипты для мобильных сервисов довольно аккуратны. Так куда дальше? Не уверен. Для этого приложения я на данный момент готов, хотя я думаю, что было бы неплохо добавить фактическую таблицу пользователей, где я могу хранить идентификаторы пользователей и помечать их как «аутентифицированные», так что не любой случайный пользователь с логином может публиковать сообщения. Это то, что я могу решить за лето.
Я загрузил биты для аутентификации в ветку auth репозитория по адресу https://github.com/Blackmist/emberapp/tree/auth, так что не стесняйтесь клонировать / раскладывать и взламывать.