В предыдущих статьях ( 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, так что не стесняйтесь клонировать / раскладывать и взламывать.
