揭开均线系统的神秘面纱
本文将使您深入了解我们许多人包含在我们的Web项目中的专有JavaScript库的内部工作原理。 在Live Connect JavaScript API和Facebook JavaScript SDK等中都可以找到社交共享按钮和联合身份验证,这只是您可能遇到的两个示例。
在本文中,您将学习使用XMLHttpRequest 2进行跨域资源共享(CORS)和REST的OAuth 2.0用户身份验证方法。 最后,我将演示一个可运行的应用程序,该应用程序允许用户在浏览器中连接并操纵其SkyDrive照片。
入门
大约两年前,我被要求向网站添加Windows Live和Facebook Connect按钮, 如图1所示。
图1.社交登录按钮
将这些按钮添加到网页需要两个库,每个提供者一个库,再加上一些JavaScript将它们连接起来。 尽管我怀疑我写的所有200 KB JavaScript是否都在使用,但是这两个库都有使它们工作的魔力。 在要求我实施第三项服务之前,我打开了Fiddler并开始检查线路上发生了什么。 经过一番摸索之后,我找到了进入文档的途径,在不知不觉中,我就有了撰写有见地的文章的前提。 因此,喝杯茶和饼干,享受阅读的乐趣。
术语表
当我们谈论将Web应用程序与其他Web服务连接在一起时,首先熟悉一下字符会很有用。
该应用程序(也称为客户端)是您的Web应用程序或您使用的网站。 该用户是使用您的应用程序的最终用户。 提供程序是您的应用程序将要连接到的Web服务,例如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 |
雅虎 | 1.0a |
领英 | 1.0a |
投寄箱 | 1.0 |
表1. 流行社交网站使用的API技术
本文重点介绍OAuth 2.0,请不要混淆它的名称。 OAuth 2.0和OAuth 1.0是完全不同的协议。 此外,许多Web服务已弃用OAuth 1.0,而改用OAuth 2.0。
OAuth2:身份验证
OAuth.net描述OAuth2的方式如下:“一种开放协议,以简单,标准的方式允许来自Web,移动和桌面应用程序的安全授权。 。 。 。 OAuth是一种发布受保护数据并与之交互的简单方法。 这也是人们向您提供访问权限的一种更安全,更安全的方式。 我们一直简化为您节省时间。”
我认为OAuth2是一种身份验证机制,可以使应用程序根据提供者的Web服务为用户获取访问令牌 。 然后,应用程序可以使用此访问令牌代表用户查询或修改提供商的数据。
启动OAuth 2
启动身份验证过程始于在提供者的网站上打开一个新的浏览器窗口,指向特殊的URL。 在此提示用户登录并同意与应用程序共享某些功能。 该过程如图2所示 ,其中提供者为https://a.com,客户端为http://b.com/。 查看地址栏中的URL,您应该在最后一个窗口中看到access_token。 图3显示了Windows Live中的登录窗口的示例。 在图中,应用程序adodson.com要求访问SkyDrive照片和文档。
图2. OAuth2流程
图3. Windows Live托管的OAuth 2同意屏幕
图3中的URL为:
https://oauth.live.com/authorize?client_id=00001111000&scope=wl.photos&response_type=
token&redirect_uri=http://b.com/redirect.html
这个特殊的URL由授权页面的初始路径和四个必需的键值参数组成:
- 当应用程序所有者注册应用程序时,由提供者提供的client_id。 (在https://manage.dev.live.com/上为Windows Live注册您的。)
- 作用域,以逗号分隔的字符串列表,表示应用程序可以访问哪些服务。 我在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
免费学习PHP!
全面介绍PHP和MySQL,从而实现服务器端编程的飞跃。
原价$ 11.95 您的完全免费
附加到URL位置哈希(#)的一些凭据:
- access_token唯一的字符串,可用于查询提供程序的API。
- expires_in一个有效的数字(以秒为单位)。
- state可以选择将其传递给state参数并返回的字符串。
使用window.location对象可以相对容易地读取凭据。 例如,可以这样提取访问令牌:
var access_token =
(window.location.hash||window.location.search).match(/access_token=([^&]+)/);
获取访问令牌后,下一步就是使用它。
OAuth2历史
OAuth 2.0是由Microsoft和Facebook的一些聪明人于2010年设计的,目的是代表用户与其他应用程序安全地共享数据服务。 它以不需要SSL以外的服务器或复杂的加密算法的方式来执行此操作。
自创建以来,OAuth2已成为事实上的方法,第三方应用程序通过它通过Windows Live或Facebook对其用户进行身份验证,然后利用这些巨型数据仓库并与之共享数据。 此后,该标准已通过Google的LinkedIn和SalesForce服务激增,而Twitter也发布了其关注点。 如您所见,OAuth2.0得到了高度认可。
本机应用
response_type = token的替代参数是response_type = code。 使用“代码”提示提供者返回短暂的授权代码,而不是访问令牌。 该代码与客户端机密结合使用(在注册应用程序时分配),然后应用程序必须进行服务器到服务器的调用以获得访问令牌。 这种方法克服了对redirect_uri施加的域限制,但可以确保它是同一应用程序。 因此,在使用无域本机应用程序时,需要使用“代码”。 使用服务器端身份验证流与本文所述的纯客户端流不同,但是它仍然是OAuth2的一部分。 您可以在IETF-OAuth2上详细了解它。
跨域资源共享(CORS)
成功获取访问令牌的应用程序现在能够向提供者的API发出签名的HTTP请求。
从另一个域访问一个域上的资源称为跨域资源共享或CORS。 这样做不像从同一域访问内容那样简单。 必须考虑遵守浏览器施加的同源策略 。 此类策略对试图访问其当前浏览器窗口的域名和端口号之外的内容的脚本施加条件。 如果不满足条件,浏览器将抛出SecurityError异常。
XHR2
JavaScript API的新形式XMLHttpRequest 2(XHR2)支持使用CORS的功能。 启用此功能有两个部分:在客户端中,请求必须使用XHR2接口,并且服务器必须使用Access-Control-Allow-Origin标头进行响应。
客户端JavaScript
以下代码说明了使用XHR2的客户端中的HTTP请求:
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标头作为响应,以满足用户浏览器中的安全策略。 例如,Windows Live API的HTTP请求URL可能会创建以下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:*允许了它。 星号(*)通配符表示允许任何Web应用程序发出的所有HTTP请求都可以从该Web服务读取响应数据。
当然,我看过的所有社交登录提供程序(例如Live Connect API和Facebook的Graph API)都会在响应中返回此标头。
XHR 2浏览器支持
并非所有流行的浏览器都支持带有CORS标头的标准XMLHttpRequest。 但是它们都支持JSONP! JSONP只需通过嵌入式脚本标签的'src'属性调用API来解决跨域的安全问题。
如果URL中提供了“ callback”参数,则所有好的API(例如SkyDrive API)都将使用函数调用来“填充”其Javascript对象响应。
首先,尽管我们可以通过侦听新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);
}
上面的代码依靠jQuery的getJSON方法作为后备,并且也做得很好。
REST:代表性状态转移
至此,您已经了解了如何通过行业标准OAuth2对用户进行身份验证以及使用XMLHttpRequest和Access-Control标头进行跨域资源共享。 接下来,我将介绍本质上对Web上的服务器和数据集的访问以及与之交互的内容。
在上一节的代码中,您看到了一个简单的HTTP请求和响应。 这与HTML页面及其资源的投放方式没有什么不同。 但是,当在应用程序中执行以与Web服务进行互操作时,我们改为将此机制称为“代表性状态转移”或REST。
要使用访问令牌对REST请求进行签名,只需将令牌包括在查询字符串参数中,如本例所示:
https://apis.live.net/v5.0/me?access_token=EwA4Aq1DBAAUlbRWyAJjK5w968Ru3C
连接点Coms
现在,到目前为止,我们已经涵盖了技术和术语,下面我们来演示一个将所有这些理论进行测试的应用程序。 不久前,我创建了一个名为Graffiti的照片编辑应用程序(参见图4 )。 我认为这是社交改造的理想竞争者,因此用户可以将其SkyDrive中的照片加载到canvas元素上并在浏览器中操作其在线照片。 您可以在http://adodson.com/graffiti/上观看该演示,也可以在https://github.com/MrSwitch/graffiti/上查看代码。
在该应用程序中,我已经在SkyDrive JavaScript SDK中重新创建了一些功能,例如WL.login,WL.filePicker和WL.api()。 如果您不熟悉这些方法,请不要担心,因为我将在进行操作时解释它们的作用。
图4. 带有SkyDrive相册中的照片的Graffiti应用程序
本质上,新功能包括以下各项:
- getToken()验证用户并存储用户的访问令牌以与SkyDrive进行交互。 这类似于WL.login()函数。
- httpRequest()用于查询SkyDrive API并获取结果,以便可以构建导航, 如图4所示 。 这类似于WL.api和WL.filePicker。
让我们更详细地看看每个。
getToken:验证
涂鸦的身份验证过程旨在按需工作。 当用户的操作需要签名的API请求时,身份验证过程开始。
在SkyDrive API中,身份验证处理程序为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功能被持久保存,以供后续调用,该功能可在现代浏览器中使用,并允许开发人员通过键值对读取和写入持久化信息(在本例中为auth-token数据)。
最初不存在令牌,因此将window.authCallback分配给该回调,并在访问令牌可用时调用。 window.open方法将创建一个弹出窗口,提供者的授权页面。 将文本“ WINDOWS_CLIENT_ID”替换为您的应用程序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);
}
getTokenfunction不能单独运行。 用户同意后,弹出的浏览器窗口将返回到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页面的完整Web地址包含访问令牌,状态和到期参数。 redirect.html页面中的脚本(如上所示)使用正则表达式从window.location.hash对象中提取参数,然后通过调用自定义函数saveToken.Finally将这些参数传递回父窗口对象(window.opener)。 ,此脚本执行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的回调。
很整洁吧? 此冗长的代码替换了Live Connect JavaScript API的WL.login函数。 首先,OAuth2流程会有点紧张和混乱,但是我认为,一旦您看到了它,便会更好地欣赏它。
接下来,让我们重新创建查询SkyDrive API的方式。
httpRequest:查询SkyDrive
Graffiti应用程序还要求用户能够查询SkyDrive并选择要在画布上绘制的文件。 WL.filpicker方法与SkyDrive JavaScript API等效。 但是,filePicker是UI方法,而对SkyDrive的REST调用通常由WL.api方法处理。 ( 图4展示了Graffiti的filePicker-esq UI。)
我创建了两个函数来将HTTP请求过程与UI分开。 在下面的代码中,函数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函数最初通过检测XHR API实例中是否存在withCredentials属性来测试XHR2的存在。 不支持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);
}
}
作为图像的项目直接返回到Graffiti的canvas元素中。
登出
本文旨在消除专有JavaScript库中打包的魔术的神秘性。 您已经看到了模仿SkyDrive JavaScript API的三个功能。
- getToken模拟WL.login
- httpRequest模拟WL.api('get',...)
- createAlbumView模拟WL.filePicker()
使用SkyDrive JavaScript SDK只是一个示例。 Facebook Connect JavaScript SDK和其他工具的工作方式非常相似。 也许现在您可以看到这些库的内容。 一系列的过继技术和聪明技巧。
这个故事还没有结束。 还有更多可以利用XMLHttpRequest的方法。 在第2部分中,我将介绍这些内容并通过扩展Graffiti应用程序以编辑专辑,将Graffiti艺术品上载到SkyDrive以及共享用户活动供稿上的信息来进行说明。 赞!
在此之前,如果您想支持在Web上聚合许多社交API的项目,请访问http://adodson.com/hello.js/,并在GitHub页面上分享您的想法。
谢谢阅读。
参考资料
本文是Internet Explorer团队HTML5技术系列的一部分。 通过3个月的免费BrowserStack跨浏览器测试@ http://modern.IE来尝试本文中的概念。
翻译自: https://www.sitepoint.com/demystifying-modern-social-apis-social-sign-in/
揭开均线系统的神秘面纱