一、前言
说到OAuth,先来一段百度到的比较官方的解释:
OAUTH协议为用户资源的授权提供了一个安全的、开放而又简易的标准。与以往的授权方式不同之处是OAUTH的授权不会使第三方触及到用户的帐号信息(如用户名与密码),即第三方无需使用用户的用户名与密码就可以申请获得该用户资源的授权,因此OAUTH是安全的。OAuth是Open Authorization的简写。
说实话,每次见到官方定义的东西 都想
(╯' - ')╯︵ ┻━┻ (掀桌子) ┬─┬ ノ( ' - 'ノ) {摆好摆好) (╯°Д°)╯︵ ┻━┻(再掀一次)
单从字面意思来讲,就是Open Authorization,即 开放 授权的意思。官方解释就是官方,看了几遍仍然不知道是什么东西,所以今天博主也根据自己的理解,完全用白话的方式,不掺杂一点官方语言来解释下OAuth2.0。用你的亲身经历举个栗子来说 就是 现在你用到的第三方登录,比如qq、微信、微博登录啊,免去了自己注册账号的烦恼,实际上这就是OAuth的实现和解决的问题。OAuth也经历了几个版本,目前到了2.0版本,也就是我们常说的OAuth2.0。
二、为什么要有OAuth
是为了解决问题啊(这不是废话吗,出来一个东西,肯定是要解决问题的),但是到底解决什么问题呢,再举个栗子,不够吃了。
2.1 解决用户身份认证问题
我们每当去访问一个网站的资源的时候,网站都会要求我们注册一个账号,访问资源的时候登录账号来表名我们的身份,注意,这一步仅仅就是为了表名你的身份,知道你是个正常的人,而不是一些机器什么的。你对网站有什么操作,网站也能很好地监测到。
这样一来,你每当去访问一个新的网站的时候都要注册账号,对用户来说,麻烦,反正我是不想注册了,对网站来说,我得记录你的账号密码什么的,也麻烦。(网站主要目的还是为了确定你的身份)。
那么问题来,能不能有一种方式,帮网站做认证来表名你是个正常的人,比如可以找一些比较出名且有权威的大公司大网站来认证一下,如qq,微博什么的,因为这些大公司已经存储了你的身份,如果你访问我的网站可以让大公司来证明一下你的身份,我知道
你是谁就可以了,也不用你在我的网站注册账号了。这样一来,你方便,我也方便。所以,OAuth2.0 给出了规范,来解决认证问题。
2.2 解决第三方网站访问已有的用户资源的问题
现在有这么一种情况,既然前边已经说了,你可以用你的qq,微博账号登录我的网站,那么我又有其他非分之想了,我想获取到你在qq或者微博上昵称啊来显示到我的网站上,这样感觉高大上一点,更有甚者,我还想要。。。。emmm ,不是那个了。
我还想要其他东西,我想读取下你的微博,说说啊什么的,或者我也想你能在我的网站发表这些东西。这样岂不是更加高大上。但是这些功能都是qq和微博提供的,我怎么能获取到权限呢。
一个办法就是 你把你的qq或者微博的账号和密码给我,我去登录,这样我就能像你一样操作各种东西。但是你可能就不愿意了,我的账号密码都给你了,万一你把我上边的小片什么的都泄漏出来,我还怎么才朋友里边做人,万一你装作我搞一些坏事情,我岂不是更要身败名裂,毕竟这是最高权限啊。还有,我这qq密码说不定就是其他各种账号的密码,你给我存起来,登录我的支付宝什么的,那怎么行。反正,就是一大堆不行。但是我要获取你qq和微博上的东西你总得给我吧,所以为了解决这个问题。OAuth就又出来了,给出来规范,来解决资源授权问题。
三、OAuth解决上述问题的流程
很明显,解决上述问题,需要三个人的参与,所以每个角色之间都得存守一定的约定和规则,这一套规则就是OAuth2.0协议的规范,具体比较官方的规范大家自己去看,本文就是以白话文的方式来讲解的,不会有那种官方的条条框框。
3.1 第三网站a与qq的协议(只以qq举例子,其他都一样)
首先qq会对他上边的用户权限进行分类,比如说获取头像,获取昵称,修改昵称,读取说说,发表说说之类的权限,都会进行整理,当a网站要获取那些用户的权限的时候就会颁发给他相应的权限,当然为了确定是a网站来访问我大企鹅用户的东西,会要求a网站在qq平台上注册一下身份,比如说用client_id和client_sercret来表名a网站应用的身份,如果是b网站 想访问也要注册一下,将来qq也会知道是b网站来访问我的用户上的资源和权限。
当然,为了更加明确和安全,qq还会要求a网站注册一个地址(redirect_url)或者注册你需要获取用户的那些权限,这样一来,你来的时候,我的检查一下redirect_uri是不是你的,当你让qq的用户登录授权成功之后,我也好根据redirect_url跳回去找你啊。
3.2 用户和qq的协议
用户来进入a网站,a网站集成了“用qq登录“登录的功能,于是用户点击用qq登录,那么a网站就会带上client_id去跳转到qq的授权页面,qq一看client_id就知道是a网站来的,核对一下(通过client_id)这个a网站是qq授权过的客户端,我根据client_id找出a网站可以获取的权限,给用户显示出来,然后让用户选择你是否同意a网站获取你的这些权限。如下图,这里我以码云 的qq登录为例
我们可以看一下这个页面的地址:https://graph.qq.com/oauth2.0/show?client_id=101284669&redirect_uri=https%3A%2F%2Fgitee.com%2Fauth%2Fqq_connect%2Fcallback&response_type=code&state=f70af08d82d762a56d844e0b0f1d0b7abafd48127c5b4ee0
1. 红色部分是qq的授权地址,绿色部分是码云携带的参数,很明显码云也向qq注册了,有client_id和redirect_uri两个重要参数,redirect_uri用户同意授权后跳回去,明显是码云的一个地址。
2. response_type=code 这个参数是OAuth2.0规范,意识是授权方式为code方式,此处一般填写固定值code,因为还有其他授权方式,但是不常用,
3. state=f70af08d82d762a56d844e0b0f1d0b7abafd48127c5b4ee0 这个也是里边的规范 状态参数 值是一个随机字符串,需要qq原样返回给码云的,置于什么作用,参考好多资料,都没有明确指出,大家也只是遵守这个规范(不过肯定有用的,现在还不清楚)
注意:当用户没有同意,OAuth的授权流程就结束了。如果同意了,才继续进行。
所以这一步流程就本就是,你带着参数向qq的授权页面跳转 https://graph.qq.com/oauth2.0/show?&client_id=xxxx&redirect_uri=xxxxx&response_type=code&state=xxxx 红色部分是你需要填写的部分。大家可以参看微信网页授权就是酱紫的。
3.3 qq的处理并且返回给a网站
qq肯定是作记录啊,记录用户对码云授予了这两个权限,将来出什么问题也好做一个凭证啊,万一你来讹qq一下说,我没有授权,你怎么把我的东西给码云了。(马云:管我什么事!!)记录之后,会生成一个code码,这个code肯定是和用户的授权记录是一一对应的,这个code码也是OAuth2.0的规范,并且这个code码有过期时间,并且只能用一次(置于为什么这么设计,先插个眼我们后边说)。然后大家可以看后续的流程:
3.3.1 首先,qq会给我发个提示:说我已经授权了,表名qq已经知道了,并且也已经留取证据了,你就不可能反咬qq一口了,这也是OAuth2.0设置这一步的目的所在。整个三个角色的态度都会有记录。
3.3.2 网址如下:https://gitee.com/auth/qq_connect/callback?code=FA0E3CBB6B699D894EE2BFD85691A561&state=f70af08d82d762a56d844e0b0f1d0b7abafd48127c5b4ee0 很明显可以看到,这个地址是之前码云填写的redirect_uri的地址,这里qq跳转回来,并携带上了code码,说明用户已经同意了你的授权,至于state可以看见还是原样返回的。
3.4 用code获取access_token
上一步中,码云已经获取到了code码,但这个 code 只能表明,用户允许码云从 qq上获取该用户的数据,如果我直接拿这个 code 去 qq访问数据一定会被拒绝,因为任何人都可以持有 code,qq并不知道 code 持有方就是码云。
所以换取access_token的时候就要码云自己也携带着自己向qq申请的账号密码来换取了,这里边已经在界面上看不到了,是再跳转的过程中默默进行的。
POST https://www.oauth.qq.com/login/oauth/access_token //去请求qq获取access_token的地址 一般都是post
params = {
code: "xxx",
client_id: "xxx",
client_secret: "xxx",
redirect_uri: "https://gitee.com/auth/qq_connect/callback"
}
当然如果一切正常,qq会给你返回如下结果
response = {
access_token: "e72e16c7e42f292c6912e7710c838347ae178b4a"
scope: "username,headimg" //这个是可以获取用户的权限的范围,是前边用户选择的部分,这里举个例子,表示只允许获取用户名和头像
token_type: "bearer",
refresh_token: "e72e1dedfs42f2923112e7710csafgsdaklfhalrf1352371537838347ae178b4a"
}
3.5 拿着access_token去调用接口
有access_token了还不为所欲为啊,获取个昵称,获取的头像,没事发个说说,读个微博啥的。到此OAuth2.0 授权流程基本就结束了。当然这个access_token也是有时间的,一般为两个小时。过期的话用返回的refresh_token刷新,因为在请求接口的过程中传输的是access_token且只有两小时的有效时间,即使是被别人截获了,损失也不会太大,设计两个小时的过期是为了安全考虑的。因为这一步的access_token是访问接口的唯一凭据,并不会判断调用者的来源。所以是有可能被非法者截获搞出一些事情的。
四、总结
1.关于code码的问题:
Q:为什么不直接发放access_token而非要返回一个code码,然后用code换取access_token,这不是多次一举吗?
A:首先有这个想法很好,证明你是一个有思想的同学。当初我也想了好半天。因为qq根据要跳回你的网站才行,而跳转到你的网站是302重定向,只能是get请求,所以要传递给码云一些数据信息的时候必须在url中添加参数,如果这时直接把access_token传递回来,就会暴露出来,这样就很不安全,而且是依赖浏览器的,所以加了一部分,只把code码传回来,这样即使code被暴露了也没问题,因为码云要在再次用code码和你的client_id,client_secret来请求qq,qq会做验证,而这一步就可以不依赖浏览器了,你可以在服务器用代码模拟http请求获取acess_token,这样就比较安全了。
Q:为什么code要设计成一次性的?
A:倘若code能无限次用,那么当用户在上述情况下收回了权限,但是由于code还能用,本身code又关联了用户的授权信息,所以码云可能再次用code来换取token,这一步并没有经过用户的允许是违法的。所以不能让code继续使用。一次code表名了用户的一次意愿,并不是终生的意愿。置于code为什么要设置有效期,我想应该是你如果获取了code不使用,qq也不会说一直帮你存着吧。
另外,当授权码发放之后,qq肯定也肯定添加并记录了用户当前对哪个网站发放了授权,这个记录也为后来的用户收回权限做准备,如果用户要收回权限,直接对相关记录操作就行。如下,是qq的授权管理:
2.关于OAuth的标准协议
有关OAuth的标准协议,各大厂商的内部实现可能不尽相同,有时候传的参数多多少少会有一点出入,但是大体流程上都是这样,而且有些必须参数都是一样的。
协议的官方内容可以参考阮一峰老师的 理解OAuth 2.0。另一篇 简述 OAuth 2.0 的运作流程 也很不错。