OAuth(开放授权)是一个关于授权的开放标准,允许用户让第三方应用访问该用户在某一网站上存储的私密的资源(如照片,视频,联系人列表),而无需将用户名和密码提供给第三方应用。目前的版本是2.0版,本文将对OAuth2.0的一些基本概念和运行流程做一个简要介绍。主要参考RFC-6749。
应用场景
这里有两个典型的例子:
- 比如你浏览某个网站的技术文章,发现其中某段介绍的不够详细,想留言给作者提问,点击
评论
,结果发现需要有这个网站的账号才能留言,此时有两个选择,一个是新注册一个此网站的账号,二是点击通过github快速登录。前者你觉得过于繁琐,直接点击了github登录,此时,OAuth的认证流程就开始了。通过引导跳转到github界面,会提示你是否授权该网站使用你的github用户信息,点击确认,跳转回原网站,发现已经使用你的github账号默认注册了一个用户,而且还不需要用户名和密码,便捷高效。 -
假如有一个云冲印的网站,可以将你存储在Google的照片冲印出来,用户为了使用该服务,必须让云冲印读取Google上的照片。为了拿到照片,云冲印必须得拿到一个用户的授权,如何获取这个用户授权呢?传统方法是用户将用户名和密码告诉云冲印,那么云冲印就可以*无限制的访问了(相当于用户自己访问),这样显然是不行的,有几个严重的缺点:
- 云冲印为了保存后续服务,会保存用户的密码,这样很不安全。
- 云冲印拥有了获取用户存储在Google的所有资料的权力,用户没法限制云冲印得到的授权范围和授权有效期。
- 用户只有修改密码,才能收回赋予云冲印的权力,但是如果还授权给了其他的应用,那么密码的修改将影响到所有被授权应用。
- 只要有一个第三方应用程序被破解,就会导致用户密码泄漏,以及所有被密码保护的数据泄漏。(例子来自阮一峰-理解OAuth2.0)
可以看出,OAuth就是为解决如上例子而诞生的。
名词解释
以下几个名词至关重要:
-
Resource Owner
:资源所有者。即用户。 -
Client
:客户端(第三方应用)。如云冲印。 -
HTTP service
:HTTP服务提供商,简称服务提供商。如上文提到的github或者Google。 -
User Agent
:用户代理。本文中就是指浏览器。 -
Authorization server
:授权(认证)服务器。即服务提供商专门用来处理认证的服务器。 -
Resource server
:资源服务器,即服务提供商存放用户生成的资源的服务器。它与认证服务器,可以是同一台服务器,也可以是不同的服务器。 -
Access Token
:访问令牌。使用合法的访问令牌获取受保护的资源。
运行流程
- (A)客户端向资源所有者请求授权。授权请求可以直接对资源所有者(如图所示)进行,或者通过授权服务器作为中介进行间接访问(首选方案)。
- (B)资源所有者允许授权,并返回凭证(如
code
)。 - (C)客户端通过授权服务器进行身份验证,并提供授权凭证(如
code
),请求访问令牌(access token
)。 - (D)授权服务器对客户端进行身份验证,并验证授权凭证,如果有效,则发出访问令牌。
- (E)客户端向资源服务器请求受保护的资源,并通过提供访问令牌来进行身份验证。
- (F)资源服务器验证访问令牌,如果正确则返回受保护资源。
授权
从运行流程不难看出,要获取access token
必须先得到用户授权(authorzation grant
),那么如果获取这么用户授权呢?OAuth 2.0定义了四种类型的授权类型:
- 授权码模式(
authorization code
) - 简化模式(
implicit
) - 密码模式(
resource owner password credentials
) - 客户端模式(
client credentials
)
授权码模式(authorization code
)
授权码模式是功能最完整、使用最广泛、流程最严密的授权模式。
由于这是一个基于重定向的流,所以客户端必须能够与资源所有者的用户代理(通常是web浏览器)进行交互,并且能够从授权服务器接收传入的请求(通过重定向)。
- (A)用户访问客户端,客户端将用户导向授权服务器,通过用户代理(
User-Agent
)发送包括它的客户端标识符、请求的范围、本地状态和一个重定向URI,授权服务器在授予(或拒绝)访问权后将其发送给用户代理。 - (B)授权服务器对资源所有者进行身份验证(通过用户代理),并确定资源所有者是否授予或拒绝客户端的访问请求。
- (C)假如资源所有者同意授权请求,那么授权服务器将会使用前面提供的或者事先指定的重定向URI(
redirection URI
),重定向到客户端,并附上一个授权码(code
)和一个前面提供的本地状态(state
)(如果有的话,则会原值返回)。 - (D)客户端收到授权码,附上早先的
重定向URI
,向授权服务器申请令牌。这一步是在客户端的后台的服务器上完成的,对用户不可见。在发出请求时,授权服务器对客户端进行身份验证。请求参数包含授权代码、用于获得验证的授权代码的重定向URI、标识客户端身份的client id
和client secret
。 - (E)授权服务器对客户端进行身份验证,验证授权代码,并确保所收到的重定向URI与用于在步骤(C)中对客户端重定向的URI相匹配,如果有效,授权服务器将发送访问令牌
access token
和刷新令牌refresh token
(可选)。
接着来介绍下各个步骤所需的参数
对于步骤A,客户端申请授权请求的URI,包含以下参数:
-
response_type
授权类型。必选项,其值固定为code
。 -
client_id
客户端id。必选项,用于标识授权服务器中已注册的客户端。 -
redirect_uri
重定向URI。可选项,如果不填写则使用注册在授权服务器端与client_id对应的redirect_uri。 -
scope
申请的权限范围,如read
或write
。可选项,如果申请的请求访问超出授权服务器定义的可操作范围则会失败。 -
state
表示客户端当前状态。可选项,可以指定任意值,授权服务器会原封不动地返回这个值。
eg:
GET /authorize?response_type=code&client_id=s6BhdRkqt3&state=xyz &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1 Host: server.example.com
C步骤中,服务器回应客户端的URI,包含以下参数:
-
code
授权码。必选项,授权码必须在颁发后很快过期以减小泄露风险,建议最长时间设为10分钟,客户端只能使用该码一次,否则会被授权服务器拒绝。该码与client id
和重定向URI,是一一对应关系。 -
state
如果客户端的请求中包含这个参数,认证服务器的回应也必须一模一样包含这个参数。
eg:
HTTP/1.1 302 Found Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA &state=xyz
D步骤中,客户端向认证服务器申请令牌的HTTP请求,包含以下参数:
-
grant_type
许可类型(授权模式)。必选项,此处固定值为authorization_code
。 -
code
上一步获得的授权码。必选项。 -
redirect_uri
表示重定向URI。必选项,且必须与A步骤中的该参数值保持一致。 -
client_id
表示客户端ID,必选项。
eg:
POST /token HTTP/1.1 Host: server.example.com Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW Content-Type: application/x-www-form-urlencoded grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
E步骤中,认证服务器发送的HTTP回复,包含以下参数:
-
access_token
表示访问令牌。必选项。 -
token_type
表示令牌类型。该值大小写不敏感,必选项,可以是bearer类型或mac类型。 -
expires_in
表示过期时间,单位为秒。如果省略该参数,必须其他方式设置过期时间。 -
refresh_token
表示更新令牌。可选项,用来获取下一次的访问令牌。 -
scope
表示权限范围。可选项,如果与客户端申请的范围一致,此项可省略。
eg:
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store Pragma: no-cache { "access_token":"2YotnFZFEjr1zCsicMWpAA", "token_type":"example", "expires_in":3600, "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA", "example_parameter":"example_value" }
简化模式(implicit
)
简化模式(implicit grant type)不通过第三方应用程序的服务器,直接在浏览器中向认证服务器申请令牌,跳过了"授权码"这个步骤,因此得名。所有步骤在浏览器中完成,令牌对访问者是可见的,且客户端不需要认证。具体步骤可参阅RFC6749 4.2节。
密码模式(resource owner password credentials
)
密码模式中,用户向客户端提供自己的用户名和密码。客户端使用这些信息,向"服务商提供商"索要授权。
在这种模式中,用户必须把自己的密码给客户端,但是客户端不得储存密码。这通常用在用户对客户端高度信任的情况下,比如客户端是操作系统的一部分,或者由一个著名公司出品。而认证服务器只有在其他授权模式无法执行的情况下,才能考虑使用这种模式。可参阅RFC6749 4.3节。
客户端模式(client credentials
)
客户端模式(Client Credentials Grant)指客户端以自己的名义,而不是以用户的名义,向"服务提供商"进行认证。严格地说,客户端模式并不属于OAuth框架所要解决的问题。在这种模式中,用户直接向客户端注册,客户端以自己的名义要求"服务提供商"提供服务,其实不存在授权问题。可参阅RFC6749 4.4节。
基于Github登录的授权码模式例子
前文提到了一个Github登录留言的例子,假设我们要使用OAuth2.0协议搭建一个网站,利用Github作为授权和资源服务器,实现第三方登录功能。
(转载)现概况一下主要流程:
1) 网站和Github之间的协商
Github会对用户的权限做分类比如读取仓库信息的权限、写入仓库的权限、读取用户信息的权限、修改用户信息的权限等等。如果我想获取用户的信息,Github会要求我,先在它的平台上注册一个应用,在申请的时候标明需要获取用户信息的哪些权限,并且在申请的时候填写你的网站域名,Github只允许在这个域名中获取用户信息。
此时我的网站已经和Github之间达成了共识,Github也给我发了两张门票,一张门票叫做Client Id
,另一张门票叫做Client Secret
。
2)用户和Github之间的协商
用户进入我的网站,点击github登录按钮的时候,我的网站会将Github给我的Client Id
交给用户,让他进入Github授权界面,如果此时用户没有登录,Github会提示登录(当然这不是OAuth2.0客户端部分应该关注的)。假设用户已经登录Github,那么Github看到用户手中的门票,就知道是我的网站让他过来的,于是就把我的网站获取的权限摆出来,并询问用户是否允许网站获取这些权限。
// 用户登录 github,协商 GET //github.com/login/oauth/authorize // 协商凭证 params = { client_id: "xxxx", redirect_uri: "http://my-website.com" }
如果用户同意,在授权页面点击了确认授权后,页面会跳转到我预先设定的 redirect_uri
并附带一个盖了章的门票code
。
// 协商成功后带着盖了章的 code Location: http://my-website.com?code=xxx
这个时候,用户和 Github 之间的协商就已经完成,Github 也会在自己的系统中记录这次协商,表示该用户已经允许在我的网站访问上直接操作和使用他的部分资源。
3)告诉Github我的网站要来访问
第二步中,我们已经拿到了盖过章的门票code
,但这个code
只能表明,用户允许我的网站从github上获取该用户的数据,如果我直接拿这个code
去github访问数据一定会被拒绝,因为任何人都可以持有code
,github并不知道code
持有方就是我本人。
还记得之前申请应用的时候github给我的两张门票么,Client Id
在上一步中已经用过了,接下来轮到另一张门票Client Secret
。
// 网站和 github 之间的协商 POST //github.com/login/oauth/access_token // 协商凭证包括 github 给用户盖的章和 github 发给我的门票 params = { code: "xxx", client_id: "xxx", client_secret: "xxx", redirect_uri: "http://my-website.com" }
拿着用户盖过章的code
和能够标识个人身份的client_id
、client_secret
去拜访 github,拿到最后的绿卡access_token
。
// 拿到最后的绿卡
response = {
access_token: "e72e16c7e42f292c6912e7710c838347ae178b4a" scope: "user,gist" token_type: "bearer", refresh_token: "xxxx" }
4)用户开始使用Github账号在我的网站上留言
// 访问用户数据 GET //api.github.com/user?access_token=e72e16c7e42f292c6912e7710c838347ae178b4a
上一步github已经把最后的绿卡access_token
给我了,通过github提供的 API 加绿卡就能够访问用户的信息了,能获取用户的哪些权限在response
中也给了明确的说明,scope
为user
和gist
,也就是只能获取user
组和gist
组两个小组的权限,user
组中就包含了用户的名字和邮箱等信息了。
// 告诉我用户的名字和邮箱
response = {
username: "barretlee", email: "barret.china@gmail.com" }
转自:https://segmentfault.com/a/1190000013467122