Эта статья даст вам представление о внутренней работе проприетарных библиотек JavaScript, которые многие из нас включают в наши веб-проекты. Кнопки общего доступа к социальным сетям и федеративная аутентификация, которые можно найти в подобных API-интерфейсах Live Connect JavaScript и Facebook JavaScript SDK, — это только два примера, с которыми вы могли столкнуться.
В этой статье вы узнаете о подходе OAuth 2.0 к аутентификации пользователей, используя XMLHttpRequest 2 для совместного использования ресурсов между источниками (CORS), а также REST. В конце я продемонстрирую работающее приложение, которое позволяет пользователям подключаться к фотографиям SkyDrive и управлять ими в браузере.
Начиная
Около двух лет назад меня попросили добавить кнопки Windows Live и Facebook Connect на веб-сайт, как показано на рисунке 1 .
Рисунок 1. Кнопки входа в социальную сеть
Для добавления этих кнопок на веб-страницу потребовалось две библиотеки, по одной от каждого поставщика, и небольшой JavaScript для их подключения. В обеих библиотеках было что-то магическое, что заставляло их работать, хотя я сомневался, что все 200 КБ JavaScript, которые я написал, используются. Прежде чем меня попросили внедрить третий сервис, я открыл Fiddler и начал проверять, что происходит по проводам. Немного покопавшись, я нашел свой путь к документам, и, прежде чем я это понял, у меня была предпосылка для проницательной статьи. Итак, возьмите чашку чая и печенье и наслаждайтесь чтением.
Словарь терминов
Когда мы говорим о подключении веб-приложения к другим веб-службам, полезно сначала ознакомиться с персонажами.
Приложение (иначе называемое клиентом) — это ваше веб-приложение или веб-сайт, который вы используете. Пользователь — это конечный пользователь, который использует ваше приложение. Поставщик — это веб-служба, к которой ваше приложение будет подключаться, например, Windows Live или Facebook. Сервер авторизации — это сервис входа пользователя в систему.
Технологии
Для аутентификации пользователей и безопасной подписки последующих запросов API используются два общепринятых отраслевых стандарта: OAuth 1.0 и OAuth 2.0. Реализации этих базовых технологий не отличаются, но URL-адреса и нюансы между поставщиками различаются. Таким образом, многие провайдеры имеют свою собственную библиотеку JavaScript для поддержки своего API, описанную в таблице 1 .
поставщик | Версия OAuth |
Windows Live API | 2 |
График Facebook | 2 |
Google API | 2 |
щебет | 1.0a |
Yahoo | 1.0a |
1.0a | |
Dropbox | 1,0 |
Таблица 1. Технологии API, используемые популярными социальными сайтами
Эта статья посвящена OAuth 2.0 — и не путайте название. OAuth 2.0 и OAuth 1.0 — это совершенно разные протоколы. Более того, многие веб-сервисы устарели как OAuth 1.0 в пользу OAuth 2.0.
OAuth2: аутентификация
Вот как OAuth.net описывает OAuth2: «Открытый протокол, обеспечивающий безопасную авторизацию простым и стандартным способом из веб, мобильных и настольных приложений. , , , OAuth — это простой способ публикации и взаимодействия с защищенными данными. Это также более безопасный и безопасный способ для людей, чтобы дать вам доступ. Мы сохранили простоту, чтобы сэкономить ваше время ».
Я рассматриваю OAuth2 как механизм аутентификации, который позволяет приложению получать токен доступа для пользователя на основе веб-службы провайдера. Затем приложение может использовать этот токен доступа для запроса или изменения данных поставщика от имени пользователя.
Инициировать OAuth 2
Инициирование процесса аутентификации начинается с открытия нового окна браузера по специальному URL на веб-сайте провайдера. Здесь пользователю предлагается войти в систему и согласиться поделиться некоторыми функциями с приложением. Процесс показан на рисунке 2 , где поставщик https://a.com, а клиент http://b.com/. Посмотрите на URL в адресной строке, где вы должны увидеть access_token в последнем окне. На рисунке 3 показан пример окна входа из Windows Live. На рисунке приложение adodson.com запрашивает доступ к фотографиям и документам SkyDrive.
Рисунок 2. Поток OAuth2
Рисунок 3. Экран согласия OAuth 2, размещенный в Windows Live
URL на рисунке 3 :
https://oauth.live.com/authorize?client_id=00001111000&scope=wl.photos&response_type=
token&redirect_uri=http://b.com/redirect.html
Этот специальный URL-адрес состоит из начального пути для страницы авторизации и четырех обязательных параметров значения ключа:
- Client_id, предоставленный провайдером, когда владелец приложения регистрирует приложение. (Зарегистрируйте свою учетную запись для Windows Live по адресу https://manage.dev.live.com/ .)
- Область действия, представляющая собой список строк, разделенных запятыми, которые обозначают, к каким службам может обращаться приложение. Я веду список возможных областей для различных поставщиков по адресу http://adodson.com/hello.js/#ScopeandPermissions .
- Атрибут response_type = token, который переводится как «Эй, немедленно верните токен доступа».
- Атрибут redirect_uri, который является адресом для перенаправления окна после того, как пользователь вошел в систему или отменил ее. Этот URL должен принадлежать тому же источнику, что и client_id, когда он был предоставлен.
Существует также необязательный параметр состояния, который представляет собой строку, которая, если она включена, просто возвращается в ответе от поставщика аутентификации.
Получение токена доступа
После того как пользователь прошел проверку подлинности и согласился поделиться с приложением, окно браузера перенаправляется на страницу, определенную в параметре redirect_uri. Например:
http://adodson.com/graffiti/redirect.html#access_token=EwA4Aq1DBAAUlbRWyAJjK5w968Ru3Cyt
К хешу местоположения URL (#) добавлены некоторые учетные данные:
- access_token Уникальная строка, которую можно использовать для запроса API провайдера.
- expires_in Число (в секундах), для которого действует access_token.
- state Строка, которая может быть дополнительно передана в параметр состояния и возвращена.
Учетные данные можно относительно легко прочитать с помощью объекта window.location. Например, токен доступа может быть извлечен так:
var access_token =
(window.location.hash||window.location.search).match(/access_token=([^&]+)/);
После получения токена доступа следующим шагом будет его использование.
История OAuth2
OAuth 2.0 был разработан в 2010 году некоторыми умными людьми из Microsoft и Facebook как средство для безопасного обмена данными с другими приложениями от имени пользователя. Это делается таким образом, что не нужно полагаться на сервер или сложные зашифрованные алгоритмы помимо SSL.
С момента своего создания OAuth2 стал де-факто методом, с помощью которого сторонние приложения аутентифицируют своих пользователей через Windows Live или Facebook, а затем подключаются и обмениваются данными с этими хранилищами мегалитических данных. С тех пор стандарт распространялся через сервисы Google, LinkedIn и SalesForce, а Twitter написал в Твиттере о своем интересе. Как видите, OAuth2.0 получил высокую оценку.
Родные приложения
Альтернативным параметром для response_type = token является response_type = code. Использование «кода» предлагает провайдеру выдать кратковременный код авторизации вместо токена доступа. Код используется в сочетании с секретом клиента (выделяется во время регистрации приложения), и приложение должно затем сделать межсерверный вызов для получения токена доступа. Этот подход позволяет обойти ограничения домена, налагаемые на redirect_uri, но гарантирует, что это то же самое приложение. Поэтому использование «кода» необходимо при работе с собственными приложениями, которые не имеют домена. Использование потока аутентификации на стороне сервера отличается от потока клиентов, описанного в этой статье, но он все еще является частью OAuth2. Вы можете прочитать об этом более подробно на IETF-OAuth2 .
Обмен ресурсами между источниками (CORS)
Приложение, успешно получив токен доступа, теперь может отправлять подписанные HTTP-запросы в API провайдера.
Доступ к ресурсам в одном домене из другого известен как совместное использование ресурсов между источниками или CORS. Это не так просто, как доступ к контенту из одного домена. Необходимо учитывать соблюдение политики того же происхождения, установленной браузером. Такая политика применяет условия к сценариям, пытающимся получить доступ к содержимому вне имени домена и номера порта их текущего окна браузера. Если условия не выполняются, браузер выдаст исключение SecurityError.
XHR2
Новое воплощение JavaScript API, XMLHttpRequest 2 (XHR2), поддерживает возможность использования CORS. Включение этой возможности состоит из двух частей: в клиенте запрос должен использовать интерфейс XHR2, а сервер должен ответить заголовком Access-Control-Allow-Origin.
Клиентский JavaScript
Следующий код иллюстрирует HTTP-запрос в клиенте с использованием XHR2:
var xhr = new XMLHttpRequest();
xhr.onload = function(e){
// contains the data
console.log(xhr.response);
};
xhr.open('GET', “http://anotherdomain.com”);
xhr.send( null );
HTTP-заголовки контроля доступа
Поставщик отвечает заголовком Access-Control-Allow-Origin, удовлетворяя политике безопасности в браузере пользователя. Например, URL-адрес HTTP-запроса к Windows Live API может создать следующий HTTP-запрос и ответ:
REQUEST
GET https://apis.live.net/v5.0/me?access_token=EwA4Aq1DBAAUlbRWyAJjK5w968Ru3Cy
...
RESPONSE
HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
...
{
"id": "ab56a3585e01b6db",
"name": "Drew Dodson",
"first_name": "Drew",
"last_name": "Dodson",
"link": "http://profile.live.com/cid-ab56a3585e01b6db/",
"gender": "male",
"locale": "en_GB",
"updated_time": "2012-11-05T07:11:20+0000"
}
Политика безопасности браузера не отклоняет этот запрос CORS, поскольку поставщик разрешил его, предоставив HTTP-заголовок Access-Control-Allow-Origin: *. Подстановочный знак звездочка (*) указывает на то, что всем HTTP-запросам из любого веб-приложения разрешено считывать данные ответов из этого веб-сервиса.
Все поставщики социальных сетей, на которые я смотрел, например, Live Connect API и Graph API Facebook, конечно, возвращают этот заголовок в своих ответах.
Поддержка браузера XHR 2
Не все популярные браузеры поддерживают теперь стандартный XMLHttpRequest с заголовками CORS. Но все они поддерживают JSONP! JSONP просто обходит проблемы безопасности Cross Domain, вызывая API через атрибут ‘src’ тега встроенного скрипта.
Все хорошие API, такие как SkyDrive API, будут дополнять свой ответ Javascript Object вызовом функции, если в URL указан параметр callback.
Во-первых, хотя мы можем использовать функцию обнаружения, отслеживая свойство нового интерфейса XHR, как в примере ниже.
If( “withCredentials” in new XMLHttpRequest() ){
// browser supports XHR2
// use the above method
}
else {
// Use JSONP, add an additional parameter to the URL saying return a callback
jQuery.getJSON(url + '&callback=?', onsuccess);
}
Приведенный выше код опирается на метод getJSON от jQuery как запасной вариант, и тоже отлично справляется.
REST: представительский государственный трансферт
До этого момента вы узнали об аутентификации пользователей с помощью отраслевого стандарта OAuth2 и совместном использовании ресурсов с помощью заголовков XMLHttpRequest и Access-Control. Далее я расскажу о том, что по сути представляет собой доступ и взаимодействие с серверами и наборами данных в Интернете.
В коде предыдущего раздела вы видели простой HTTP-запрос и ответ. Это мало чем отличается от того, как обслуживаются HTML-страницы и их ресурсы. Однако, когда выполняется внутри приложения для взаимодействия с веб-сервисами, мы вместо этого называем этот механизм передачей представительского состояния или REST.
Чтобы подписать запрос REST токеном доступа, просто включите токен в параметры строки запроса, как в этом примере:
https://apis.live.net/v5.0/me?access_token=EwA4Aq1DBAAUlbRWyAJjK5w968Ru3C
Подключение Dot Coms
Теперь, когда технология и терминология уже рассмотрены, давайте приступим к демонстрации приложения, которое проверяет всю эту теорию. Некоторое время назад я создал приложение для редактирования фотографий под названием Graffiti (см. Рисунок 4 ). Я чувствовал, что это идеальный претендент на социальную перестройку, чтобы пользователи могли загружать свои фотографии из SkyDrive на элемент canvas и манипулировать своими онлайн-фотографиями в браузере. Вы можете посмотреть демо на http://adodson.com/graffiti/, а также проверить код на https://github.com/MrSwitch/graffiti/ .
В приложении я заново создал некоторые функции в SkyDrive JavaScript SDK, такие как WL.login, WL.filePicker и WL.api (). Если вы не знакомы с этими методами, не беспокойтесь, потому что я объясню, что они делают по ходу дела.
Рисунок 4. Приложение Graffiti с фотографиями из альбома SkyDrive
По сути, новая функциональность включает в себя следующие элементы:
- getToken () Аутентифицирует пользователя и сохраняет маркер доступа пользователя для взаимодействия со SkyDrive. Это похоже на функцию WL.login ().
- httpRequest () Для запросов к API SkyDrive и получения результатов для построения навигации, как на рисунке 4 . Это похоже на WL.api и WL.filePicker.
Давайте посмотрим на каждую более подробно.
getToken: аутентификация
Процесс аутентификации Graffiti предназначен для работы по требованию. Когда действие пользователя требует подписанного запроса API, начинается процесс аутентификации.
В API SkyDrive обработчиком аутентификации является WL.login. Следующий код включает пользовательскую функцию (getToken), которая воссоздает этот метод. Он применяется во всем коде приложения Graffiti и предшествует любым запросам API, как и его аналог. Вы можете увидеть типичный вызов, показанный здесь:
btn.onclick = function(){
getToken("wl.skydrive", function(token){
// … do stuff, make an API call with the token
});
}
Функция getToken, показанная в следующем коде, отслеживает сохраненный токен и запускает поток аутентификации, когда требуется авторизация. Полученные токены сохраняются для последующих вызовов с помощью новой функции HTML5 localStorage , которая доступна в современных браузерах и позволяет разработчикам читать и записывать постоянную информацию (в данном случае наши данные аутентификационного токена) через пары ключ-значение.
Изначально токенов не существует, поэтому window.authCallback назначается для обратного вызова и вызывается, когда токен доступа доступен. Метод window.open создает всплывающее окно на странице авторизации провайдера. Замените текст «WINDOWS_CLIENT_ID» на идентификатор вашего приложения.
function getToken(scope, callback){
// Do we already have credentials?
var token = localStorage.getItem("access_token"),
expires = localStorage.getItem("access_token_expires"),
scopes = localStorage.getItem("access_scopes") || '';
// Is this the first sign-in or has the token expired?
if(!(token&&(scopes.indexOf(scope)>-1)&&expires>((new Date()).getTime()/1000))){
// Save the callback for execution
window.authCallback = callback;
// else open the sign-in window
var win = window.open( 'https://oauth.live.com/authorize'+
'?client_id='+WINDOWS_CLIENT_ID+
'&scope='+scope+
'&state='+scope+
'&response_type=token'+
'&redirect_uri='+encodeURIComponent
(window.location.href.replace(//[^/]*?$/,'/redirect.html')),
'auth', 'width=500,height=550,resizeable') ;
return;
}
// otherwise let’s just execute the callback and return the current token.
callback(token);
}
Функция getToken не работает сама по себе. После того, как пользователь дал согласие, всплывающее окно браузера возвращается на страницу redirect.html с новым токеном доступа в пути. Этот HTML-документ показан в следующем коде.
<!DOCTYPE html>
<script>
var access_token =
(window.location.hash||window.location.search).match(/access_token=([^&]+)/);
var expires_in =
(window.location.hash||window.location.search).match(/expires_in=([^&]+)/);
var state = (window.location.hash||window.location.search).match(/state=([^&]+)/);
if(access_token){
// Save the first match
access_token = decodeURIComponent(access_token[1]);
expires_in = parseInt(expires_in[1],10) + ((new Date()).getTime()/1000);
state = state ? state[1] : null;
window.opener.saveToken( access_token, expires_in, state );
window.close();
}
</script>
Полный веб-адрес страницы redirect.html содержит токен доступа, аргументы состояния и срок действия. Сценарий на странице redirect.html (показанный ранее) извлекает аргументы из объекта window.location.hash с помощью регулярного выражения, а затем передает их обратно в родительский объект окна (window.opener), вызывая пользовательскую функцию saveToken.Finally. , этот скрипт выполняет window.close () для удаления всплывающего окна, потому что оно больше не нужно. Вот код для saveToken:
function saveToken(token, expires, state){
localStorage.setItem("access_token", token );
localStorage.setItem("access_token_expires", expires );
// Save the scopes
if((localStorage.getItem("access_scopes") || '').indexOf(state)===-1){
state += "," + localStorage.getItem("access_scopes") || '';
localStorage.setItem("access_scopes", state );
}
window.authCallback(token);
}
Функция saveToken сохраняет учетные данные access_token в localStorage. Наконец, обратный вызов, сохраненный в window.authCallback, запускается.
Довольно аккуратно, а? Этот длинный код заменяет функцию WL.login API Live Connect JavaScript. Сначала поток OAuth2 немного интенсивен и запутан, но я думаю, что, увидев его в действии, вы оцените его лучше.
Далее, давайте воссоздадим способ, которым мы запрашиваем API SkyDrive.
httpRequest: запрос SkyDrive
Приложение Graffiti также требует, чтобы пользователь мог запрашивать SkyDrive и выбирать файл для рисования на холсте. Метод WL.filpicker является эквивалентом JavaScript API SkyDrive. Однако filePicker является методом пользовательского интерфейса, тогда как вызов REST для SkyDrive обычно обрабатывается методом WL.api. ( Рисунок 4 иллюстрирует пользовательский интерфейс Graffiti filePicker-esq.)
Я создал две функции, чтобы отделить процесс HTTP-запроса от пользовательского интерфейса. В следующем коде функция httpRequest эмулирует метод WL.api (‘get’, ..):
function httpRequest(url, callback){
// IE10, FF, Chrome
if('withCredentials' in new XMLHttpRequest()){
var r = new XMLHttpRequest();
// xhr.responseType = "json";
// is not supported in any of the vendors yet.
r.onload = function(e){
callback(JSON.parse(r.responseText});
}
r.open("GET", url);
r.send( null );
}
else{
// Else add the callback on to the URL
jsonp(url+"&callback=?", callback);
}
}
Функция httpRequest первоначально проверяет наличие XHR2, определяя, существует ли свойство withCredentials в экземпляре API XHR. Резервным вариантом для браузеров, которые не поддерживают возможности XHR2 для перекрестного источника, является JSONP (см. JQuery.getJSON ).
Обработчик xhr.onload преобразует строку ответа в объект JavaScript и передает ее в качестве первого параметра обработчику обратного вызова. Функция httpRequest легко инициируется.
httpRequest(“https://apis.live.net/v5.0/me?access_token=EwA4Aq1DBAAUlbRWyAJjK5w968Ru3Cy”,
callback);
Функция, которая вызывает httpRequest и впоследствии помещает миниатюрные изображения на экран, является createAlbumView, и именно этот метод повторно создает функциональность, подобную WL.filePicker, например:
createAlbumView("me/albums", "SkyDrive Albums");
Вот код для createAlbumView:
function createAlbumView(path, name){
// Get access_token from OAuth2
getToken("wl.skydrive", function(token){
// Make httpRequest
// Retrieve all items from path defined in arguments
httpRequest('https://apis.live.net/v5.0/'+path+'?access_token='+token, function(r){
// Create container
// …
// Loop through the results
for(var i=0;i<r.data.length;i++){
// Create thumbnail and insert into container
createThumbnail(r.data[i], container);
}
});
});
}
Когда предоставлено имя пути к альбому (например, «я / альбомы»), createAlbumView заполняет экран навигации элементами, найденными по этому адресу. Хотя первоначальный список альбомов доступен на «я / альбомы», createAlbumView является рекурсивным. Элементы, которые он находит в виде альбомов, образуют новый путь и, следовательно, делают весь SkyDrive ориентированным на навигацию. В следующем коде показано, как элемент отображает свой тип и как он обрабатывается приложением:
function thumbnail_click (item){
if( item.type === "photo" ){
applyRemoteDataUrlToCanvas( item.source );
}
else if(item.type === "album"){
createAlbumView(item.id+'/files', item.name);
}
}
Элементы, которые являются изображениями, возвращаются прямо в элемент холста Граффити.
Выписка
Эта статья была нацелена на то, чтобы демистифицировать магию, упакованную в проприетарных библиотеках JavaScript. Вы видели три функции, которые имитируют их из JavaScript API SkyDrive.
- getToken эмулирует WL.login
- httpRequest эмулирует WL.api (‘get’,…)
- createAlbumView эмулирует WL.filePicker ()
Использование SkyDrive JavaScript SDK было только примером. Facebook Connect JavaScript SDK и другие работают очень похожим образом. Возможно, теперь вы можете увидеть эти библиотеки, какие они есть; коллекция приемных технологий и хитрых хитростей.
Эта история не закончена. Есть и другие способы использования XMLHttpRequest. Во второй части я представлю их и проиллюстрирую их, расширив приложение Graffiti для редактирования альбомов, загрузив художественные работы Graffiti в SkyDrive и поделившись информацией в ленте активности пользователей. Magnifico!
До этого, если вы хотите поддержать проект, объединяющий множество социальных API в Интернете, просмотрите http://adodson.com/hello.js/ и поделитесь своими мыслями на странице GitHub.
Спасибо за прочтение.
Ссылки
- Исходный код граффити
- OAuth 2 Intro
- Windows Live Connect API
- Объект XMLHTTPRequest
- Обнаружение поддержки XHR2
- SkyDrive API
- Библиотека HelloJS
Эта статья является частью технической серии HTML5 от команды Internet Explorer. Испытайте концепции этой статьи с 3 месяцами бесплатного кросс-браузерного тестирования BrowserStack @ http://modern.IE .