OAuth2.0原理与实现
弄懂了原理流程,才可以搭建出来。更重要的是,可以根据原理流程自定义搭建,甚至可以完全自己实现一套,最后运行效果和原理和这个对得上就成功了,不要总期待标准答案!
首先参考两篇博客:
图分析补充:
第一步访问授权页面(然后授权服务器返回授权页面)时,即需要预先申请的开发者信息如client_id、redirect_uri,而授权服务器在第三步用户手动授权后才用到这些信息,进行页面跳转(response状态码302,附上redirect地址和授权码等),第四步是第三部redirect_uri所指向的应用开发后台地址,是第三步的网页自动重定向所访问到的,这个后台逻辑要自己写,内容是拼接redirect_uri和授权码code参数,访问认证服务器后台,申请令牌返回。这样应用获取到令牌,此后使用令牌继续访问资源服务器获取资源进行使用,由此跳转到自己的应用页面(一般是一个绑定手机号页面,使用微信账号与之绑定,是一个注册,把微信账号access_token与手机号账号绑定,存入应用自身数据库,当然access_token存在有效期,过期后也需要在用户再次登录时在自身数据库透明更新)
(A)用户访问客户端,后者将前者导向认证服务器。
(B)用户选择是否给予客户端授权。
(C)假设用户给予授权,认证服务器将用户导向客户端事先指定的"重定向URI"(redirection URI),同时附上一个授权码。
(D)客户端收到授权码,附上早先的"重定向URI",向认证服务器申请令牌。这一步是在客户端的后台的服务器上完成的,对用户不可见。
(E)认证服务器核对了授权码和重定向URI,确认无误后,向客户端发送访问令牌(access token)和更新令牌(refresh token)。
下面是上面这些步骤所需要的参数。
A步骤中,客户端申请认证的URI,包含以下参数:
- response_type:表示授权类型,必选项,此处的值固定为"code"
- client_id:表示客户端的ID,必选项
- redirect_uri:表示重定向URI,可选项
- scope:表示申请的权限范围,可选项
- state:表示客户端的当前状态,可以指定任意值,认证服务器会原封不动地返回这个值。
下面是一个例子。
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分钟,客户端只能使用该码一次,否则会被授权服务器拒绝。该码与客户端ID和重定向URI,是一一对应关系。
- state:如果客户端的请求中包含这个参数,认证服务器的回应也必须一模一样包含这个参数。
下面是一个例子。
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,必选项。
下面是一个例子。
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:表示权限范围,如果与客户端申请的范围一致,此项可省略。
下面是一个例子。
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" }
从上面代码可以看到,相关参数使用JSON格式发送(Content-Type: application/json)。此外,HTTP头信息中明确指定不得缓存。
具体到应用开发上,无论是网站还是手机App,原理都是相同的:用户访问应用(比如微信登录功能),应用(使用申请的开发者信息如client_id、redirect_uri)访问资源服务器(微信后台,一个例子是很多App直接请求打开微信客户端,那么必然是访问微信后台),返回授权页面(微信授权)。用户输入资源服务器用户名/密码(微信),或直接获取已登录账号(比如手机App微信账号),点击授权(认证服务器验证微信账号信息)。授权后根据该应用此前传递的redirect_uri重定向到应用页面并附上授权码(code),应用使用重定向的网址和授权码访问应用自己的后台,由后台再不可见地使用redirect_uri、code到认证服务器获取令牌(access_token,可选refresh_token)返回。
一个细节是返回授权页面,传统方式是应用内嵌一个微信用户名/密码登录页面(由微信后台返回),现在一般使用二维码扫描方式。
一个例子是微信网页版的微信授权登录:
根据https://www.oschina.net/question/1172551_218058和亲自追踪,采用的是二维码ajax轮询方式:
“打开https://wx.qq.com/用FireFox看吧,它不断轮询https://login.weixin.qq.com/cgi-bin/mmwebwx-bin/login?uuid=xxxx&tip=1&_=1419316260694
用微信一扫这个结果就变了。”
“ajax轮训,前台每隔5秒访问一次后台,如果返回1这跳转,如果不是则继续查询
手机扫描二维码实际上是访问一个链接,当成功扫描,则把上一个链接返回1.这样电脑端就可以跳转了”
这个网页(打开后)上的二维码实际是不断轮询ajax访问微信后台的:
微信扫码后,微信后台一个字段改变(如此感知扫码),应用会访问到该微信用户信息(比如图片),等待授权,地址不跳转(不变),同时会继续轮询此前轮询的微信后台地址:
而扫码后的手机端,最终访问的是微信后台返回的授权页面(实际带上了应用的client_id、redirect_uri信息,经过了应用后台的转发)。手机端点击授权之后,微信后台有另一个特定标示字段的改变(数据库或缓存),这样网页二维码页面的ajax长轮询(无论的Comet技术还是WebSocket,均可立即推送这个字段值改变)就可访问到这个改变的字段,根据微信后台授权逻辑,就可立即跳转到授权后的页面(redirect_uri):
这就是网页自动跳转的原理。其实后台透明地经历了另一个获取令牌的过程,让网页直接根据令牌访问到了该微信用户(资源服务器)的有限制资源(这里特殊,几乎是该用户的所有资源)。
App应用(区别于网站应用)的授权过程表现出请求手机系统(表现为出现弹出框让用户同意)访问微信客户端,使用微信客户端(内嵌在App应用中,所以此时实际仍然在该App应用中进行操作,所以才有后来的跳转回App应用申请完授权资源后的页面)访问微信后台,返回一个授权页面(应用内访问微信,实际仍然在应用页面里)。用户点击授权后,自动跳转到应用页面(现在很多应用都有一个手机号绑定,短信验证码验证后进行绑定的页面,点击后进入真正的应用页面,一般是个人主页)。其实后台经历了一个获取授权码(authorization code)、重定向、获取令牌(access token),再用令牌有限制访问资源(仍然指微信账号信息)的过程。
令牌(access token)一般有很短的有效期(2小时),需要使用refresh_token来定期刷新,refresh_token有效期较长(30天),失效后需要用户重新授权。表现为退出登录后再微信登录,短期内不再需要用户再手动进行微信授权,说明短期内(access token有效)仍然有权访问该有限制资源(微信账号信息)。
后台需要有用户名/密码认证、授权机制,保存令牌的数据库或缓存,令牌生成接口,刷新令牌接口,过期自动删除令牌机制(参考Spring SSO的JWT令牌配置和实现),资源服务权限配置,验证令牌放行或拒绝资源访问机制。搞清这套逻辑和技术实现原理,尝试自定义实现或完全自主实现。
标准OAuth2.0系统参考: