集成基于OAuth协议的单点登陆
在之前的一篇文章中,我们已经介绍了如何为一个应用添加对CAS协议的支持,进而使得我们的应用可以与所有基于CAS协议的单点登陆服务通讯。但是现在的单点登陆服务实际上并不全是通过实现CAS协议来完成的。例如Google就使用OAuth协议来管理它的帐户。
相较于CAS协议,OAuth协议不仅仅可以完成对用户凭证的验证,更可以提供权限管理的功能。在这些权限管理功能的支持下,一个应用甚至可以访问其它使用相同OAuth服务的应用的数据,从而完成应用间的交互。
OAuth集成示例
现在我们就来看一个通过OAuth协议来访问其它应用资源的示例。
相信您或者您的许多朋友都已经在微信中关联了LinkedIn帐号,从而可以显示相应LinkedIn帐号的一些信息。要做到这一点,我们首先需要在微信的“设置”-> “通用”-> “功能”-> “LinkedIn”下找到关联LinkedIn帐号的功能:
在点击了关联帐号以后,我们将跳转到LinkedIn。此时LinkedIn会要求我们输入LinkedIn帐号的用户名和密码,进而允许微信访问LinkedIn帐号的部分信息:
再跳转回到微信的时候,我们就可以看到微信已经能够访问相应的LinkedIn帐号的相关信息了:
关联流程简介
那微信和LinkedIn之间的关联是如何建立的呢?答案就是OAuth协议。在本节中,我们将对OAuth协议的运行流程进行简单地介绍。
注:我个人所做的工作是为Web应用添加OAuth集成。为该类型应用集成OAuth协议从流程上来说相对简单,反过来却无法较为全面地展现OAuth协议的整体运行流程。因此在本节中我们假设手机中运行的微信是一个“confidential clients”,并可以“capable of interacting with the resource owner's user-agent (typically a web browser) and capable of receiving incoming requests (via redirection)”(RFC6749 – Section 4.1)。
实际上,在用户点击微信中的“关联帐号”这个按钮的时候,微信可能就开始启动手机浏览器并转向下面的地址:
https://{linkedin_oauth_url}/authorize?response_type=code&client_id=CLIENT_ID&redirect_uri=CALLBACK_URL&scope=read&state=xxx
该URL中包含了五个较为重要的URL参数:
- client_id。表示微信在LinkedIn中所对应的应用ID
- redirect_uri。表示在得到了用户授权后所需要返回到的地址
- scope。表示当前应用所申请的权限
- response_type。表示当前应用所请求的Authorization Grant的类型
- state。表示当前应用的状态
一旦成功转向了该URL,那么手机浏览器将显示LinkedIn的用户登录并赋予权限的页面。如果用户成功登陆并允许微信访问这些信息,那么我们的浏览器将会被重定向到redirect_uri所标示的地址。再该地址中还会通过URL参数code来记录所得到的Authorization Grant:
https://{redirect_uri}?code=AUTHORIZATION_CODE
在浏览器重定向到{redirect_uri}之后,微信就知道用户已经给与了它访问当前用户的LinkedIn信息的权限,并通过URL参数code来分析出刚从OAuth服务中得到的AUTHORIZATION_CODE。接下来,微信就会通过刚刚得到的AUTHORIZATION_CODE尝试向下面的地址发送请求,以请求访问资源所需要的令牌:
https://{linkedin_oauth_url}/token?client_id=CLIENT_ID&code=AUTHORIZATION_CODE&grant_type=authorization_code&redirect_uri=CALLBACK_URL
上面的请求中包含了四个URL参数:
- client_id:表示微信在LinkedIn中所对应的应用ID
- code:用来标示我们刚刚从服务端所得到的Authorization Grant
- grant_type:用来标示Authorization Grant的类型
- redirect_uri:用来标示成功获得令牌的情况下所需要返回的URL
在应用成功地得到了访问资源的令牌之后,LinkedIn将返回一个包含了资源访问令牌的响应。同时浏览器将被重定向到redirect_uri所标示的位置。
接下来,应用就可以通过刚刚获得的资源访问令牌来访问目标资源了。
总的来说,微信(Wechat)通过OAuth协议获得用户帐号信息的流程如下所示:
OAuth协议集成
在经过前面的示例讲解之后,相信读者们已经大概了解了OAuth协议的大致运行流程了。那么我们现在就可以开始考虑集成OAuth协议了?是的。但是在这之前,我们首先要在OAuth服务上注册应用。
不幸的是,LinkedIn似乎没有对普通用户公开应用注册的接口。因此我们将以Digital Ocean来演示如何在一个OAuth服务上注册一个应用。
首先,请在浏览器的地址栏中键入https://www.digitalocean.com/以来到Digital Ocean的主页面,并注册/登录。接下来,在登入的界面中点击上方的“API”,以进入应用管理页面:
在该页面中,我们需要点击“Register New Application”来进入应用注册页面。而在该注册页面中,我们需要提供应用的一系列信息。这些信息包括:
- 应用的名称
- 应用的地址
- 应用的重定向URL,即获得了Authorization Code或资源访问令牌后应用所需要重定向到的默认地址
注册完毕以后,我们就会得到一个应用的ID以及相应的应用凭证。这一对信息用来在OAuth协议运行过程中证明应用的合法性。
现在我们就可以开始尝试在应用中集成对OAuth协议的支持了。在OAuth协议的第一步,我们需要向OAuth服务提供者发送权限请求,以要求用户赋予访问信息的权限。就像流程简介中所讲的那样,该请求的目标URL常常包含五个参数:
- client_id。表示应用在OAuth服务中注册时所得到的应用ID
- redirect_uri。表示在应用得到了用户授权后所需要返回到的地址
- scope。表示当前应用所申请的权限
- response_type。表示当前应用所请求的Authorization Grant的类型
- state。表示应用的当前状态。通过该参数,应用可以恢复至特定状态
这里的五个参数都非常容易理解,但它们仍然有需要注意的地方。首先要说明的一点就是redirect_uri。它实际上是一个可选参数。在没有标明该参数的情况下,OAuth服务所返回的重定向响应就将返回到我们刚刚在应用注册时所指定的URL。只是在一般情况下,我们都会在发送权限请求的时候显式地标明该参数。这在应用使用了负载平衡,分区域部署等解决方案的情况下可以更好地对应用的性能,服务的负载等众多方面进行控制。
接下来要说的URL参数就是scope。很不幸地,在OAuth协议中并没有规定该参数的取值范围。也就是说,不同的OAuth服务所接受的scope参数的取值范围可能并不相同。这听起来可能令人有些沮丧。但是如果OAuth协议集成框架实现得比较好的情况下,该URL参数完全可以作为框架的一个可配置的参数暴露给框架的使用者。
最后要说的就是response_type参数。OAuth协议是一个可以在多种场景下使用的协议,如Web应用,手机应用等。它们在不同的应用场景下面对的风险和需求都各不相同,从而产生了不同的获得资源访问令牌的方法。因此您需要根据应用的实际情况选择合适的参数。而在这里,我们选择使用code,即Authorization Code Grant。因为其流程最为复杂,而且还支持资源访问令牌的刷新。
在向OAuth服务发送了请求之后,如果浏览器能够正确地显示OAuth服务要求用户赋予权限的页面,那就表示对OAuth服务的第一步集成已经成功了。
在正确地输入了用户名和密码,并同意应用的访问请求后,应用将接收到一个重定向响应。在接收到该响应之后,应用将记录URL参数code所标明的权限请求所得到的令牌,并使用state参数来恢复应用的状态。
如果成功地得到权限令牌并恢复了应用状态,我们就需要开始下一步工作:通过权限令牌来得到资源访问令牌。在这一步中,我们需要像OAuth服务器发送如下形式的请求:
https://{linkedin_oauth_url}/token?client_id=CLIENT_ID&code=AUTHORIZATION_CODE&grant_type=authorization_code&redirect_uri=CALLBACK_URL
该请求中常常包含四个URL参数:
- client_id:表示应用在OAuth服务中所对应的应用ID
- code:用来标示我们刚刚从服务端所得到的权限令牌
- grant_type:用来标示Authorization Grant的类型
- redirect_uri:用来标示成功获得资源访问令牌的情况下所需要返回的URL
如果请求成功地得到了服务的响应,那么我们就需要从响应中分析得到资源访问令牌access_token以及其它的一系列信息:
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache
{
"access_token": "2YotnFZFEjr1zCsicMWpAA",
"token_type": "code",
"expires_in": 3600,
"refresh_token": "tGzv3JOkF0XG5Qx2TlKWIA",
"example_parameter": "xxx"
}
在成功地从响应中解析出了资源访问令牌以后,我们就可以尝试着向资源服务器发送资源访问请求了。在该请求中,我们只需要将资源访问令牌作为请求的Authorization头即可。如果资源能够成功返回,那么表示我们对OAuth协议的集成已经基本成功了。
Refresh Token
当然,工作还没有完全结束。在从OAuth服务器所得到的响应中还有一个较为重要的信息:refresh_token。它用来从OAuth服务那里再次获得一个资源访问令牌。这在原有的资源访问令牌过期的情况下非常有用。
在需要通过refresh token更新资源访问令牌的时候,我们需要向OAuth服务再次发送一个获取资源访问令牌的请求。只是在该请求中,我们需要将URL参数grant_type设置为“refresh_token”,并通过URL参数refresh_token来标明之前得到的refresh_token。如果OAuth服务能够返回一个正确的响应并且能够通过该响应中包含的新的资源访问令牌访问资源服务器上的资源,那么就表示我们已经完成了对refresh token的支持。
Reference
OAuth协议:http://tools.ietf.org/html/rfc6749
转载请注明原文地址并标明转载:http://www.cnblogs.com/loveis715/p/4491456.html
商业转载请事先与我联系:silverfox715@sina.com
注:
- 本文在讲解OAuth流程时所列出的各个URL只用来作为讲解使用,而不作为任何考证依据。一切URL格式都应以相应版本协议中所定义的URL格式为准
- 本文在介绍微信与LinkedIn关联的图片来自于百度经验