当你运营愈来愈多的项目,每一个项目的业务都不同,每一个须要使用到这些业务的用户就须要注册方能进行使用。若是用户还须要使用其余项目的功能,就必须还得注册使用。形成反复注册,反复登陆的不友好体验。javascript
此时不少人都会想着使用ucenter等方式来整合用户,将用户放到一个数据中心来管理。全部的项目都将使用这一个公共的数据中心来运行。ucenter和各个项目之间通讯将采用隐式api的方式进行通讯,全部的行为都在后端进行,比如你的淘宝帐号能够在天猫登陆同样。在这里,假设你也作了一个业务网站,你的网站用户须要一键分享到他的淘宝主页中、或者须要读取卖家的商品信息,那么总不能在你的网站让用户提供他的淘宝帐号密码,这显得尤其的不专业和不安全。oauth2就恰好解决了这一问题,让用户使用淘宝的快捷登陆进行受权后由淘宝提供的接口来返回用户相关信息,并将这一信息在本身数据库中生成对应的一份关联起来,这一切就变得比较安全,比较合理了。php
本文的主题是oauth2,oauth是一种协议,2是版本,即oauth2是一个协议标准,将多个项目整合起来,提供所需的接口通讯,oauth2能够创建在ucenter上能够创建在单独业务上,总之他只是一种协议,具体关于该协议的介绍请直接浏览官网或者百科介绍。html
上文中有提到淘宝的快捷登陆,那么对应就还有QQ快捷登陆(QQ互联)、新浪快捷登陆、google帐号登陆等。这样一来就可使得本来来自QQ、来自google的用户无需在你的网站进行注册后,直接间接使用原先的媒体帐号登陆就能够在你的网站访问。用google举例,你的网站可使用google帐号快捷登陆,而且能够得到google提供的一些接口服务,那么将这一关系中的google称为server端,你的网站称之为client端,下文将client称为应用。
当前主要是说本身构造一个oauth2 server端,就是上文中所提到的QQ互联、微博等,能够给本身的其余应用或者开放给第三方应用来提供方便用户管理的一个系统。前端
上图是Oauth2运行流程,描述了四个角色之间交互的方式。摘自RFC 6749java
(A):Client 向 Resoure Owner 请求受权。能够理解为用户发起请求。sql
(B):在Server端用户赞成给予 Client 端受权,并返回至 Client 端告知。数据库
(C):Client携带Authorization Grant 向Authorization Server请求获取Access Token。后端
(D):受权验证经过后,返回Access Token。api
(E):Client携带Access Token向Resource获取相关资源,例如头像、昵称、相册等。跨域
(F):Access Token 验证 经过后 Resource Server 所需获取资源。
了解了基本的流程后,来简单构造一下数据表。
/** * app 应用 */ CREATE TABLE uc_app( app_id int primary key auto_increment, app_sign varchar(20) NOT NULL DEFAULT '' COMMENT '应用惟一ID', app_secret varchar(60) NOT NULL DEFAULT '' COMMENT '通讯密码', app_name varchar(20) NOT NULL DEFAULT '' COMMENT '应用名称', app_desc varchar(60) NOT NULL DEFAULT '' COMMENT '应用描述', app_providers varchar(60) NOT NULL DEFAULT '' COMMENT '提供商', app_url varchar(255) NOT NULL DEFAULT '' COMMENT '官方主页', app_type tinyint NOT NULL DEFAULT '1' COMMENT '应用类型 1.官方应用 2.第三方应用', app_status tinyint NOT NULL DEFAULT '1' COMMENT '应用状态 1.正常运行 2.中止运行 5.审核中', allow_domain varchar(255) NOT NULL DEFAULT '' COMMENT '受权域名', allow_ip varchar(100) NOT NULL DEFAULT '' COMMENT '容许IP', release_time int(10) NOT NULL DEFAULT '0' COMMENT '发布时间', expire_time int(10) NOT NULL DEFAULT '0' COMMENT '失效时间', UNIQUE KEY (`app_sign`) ); /** * user_key 用户受权code */ CREATE TABLE uc_user_key( id int primary key auto_increment, code_key varchar(60) NOT NULL DEFAULT '', app_sign varchar(20) NOT NULL DEFAULT '' COMMENT '应用惟一ID', user_id int NOT NULL DEFAULT '0' COMMENT '用户ID', redirect_uri varchar(255) NOT NULL DEFAULT '' COMMENT '回调URI', scope varchar(20) NOT NULL DEFAULT '' COMMENT '申请权限范围', state varchar(20) NOT NULL DEFAULT '' COMMENT '客户端状态', release_time int(10) NOT NULL DEFAULT '0' COMMENT '生成时间', expire_time int(10) NOT NULL DEFAULT '0' COMMENT '失效时间', status tinyint NOT NULL DEFAULT '0' COMMENT '状态:0.待验证 1.已验证 2.已过时', UNIQUE KEY(`code_key`) ); /** * uc_user_token * 用户访问权限表 */ CREATE TABLE uc_user_token( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL DEFAULT '0' COMMENT '用户ID', app_sign VARCHAR(20) NOT NULL DEFAULT '' COMMENT '应用惟一标识', access_token VARCHAR(60) NOT NULL DEFAULT '' COMMENT '权限操做TOKEN', scope VARCHAR(20) NOT NULL DEFAULT '' COMMENT '所申请权限', release_time INT(10) NOT NULL DEFAULT '0' COMMENT '生效时间', expire_time INT(10) NOT NULL DEFAULT '0' COMMENT '失效时间', UNIQUE KEY(user_id, app_sign) );
程序流程
1.第三方应用选择快速登陆,跳转至Server端进行受权登陆。
url:http://ucenter.example.com/authorize?response_type=code&client_id=f2292b656df429d4&state=xyz& redirect_uri=http%3a%2f%2fclient.example.com%2fuser.php
若是是已经登陆状态,则自动跳过登陆过程,不然展示登陆页面,引导用户进行登陆。
2.用户登陆完毕以后,生成一个相对用户相对client_id相对回调信息的记录到数据库,并将这条记录的惟一值code返回给应用。
url:http://client.example.com/user?code=Q8GlvX4lS3Fy3KBHpQro&state=xyz
3.用户携带上一步获取的code, 到服务端进行受权,获取通讯口令。
参数 | 是否必须 | 含义 |
grant_type | 必须 | 受权类型,此值固定为“authorization_code” |
client_id | 必须 | 第三方应用的app_sign。 |
client_secret | 必须 | 第三方应用的app_secret。 |
code | 必须 | 上一步返回的authorization code。 |
redirect_uri | 必须 | 与上一步中的回调地址一致。 |
url:http://ucenter.example.com/token Method:POST Form Data grant_type:authorization_code code:Q8GlvX4lS3Fy3KBHpQro redirect_uri:http://client.example.com/user.php
4.服务端校验code事后,颁发通讯令牌 access_token 。
Response: { response: - { code: 200, msg: "success" }, datas: - { "access_token":"kIJAkUd31F58nGpknvoW3L4r3S21koNd9Lk0rfcn", "token_type":"example", "expires_in":3600,//有效期 "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",//可选项,存在表示下一次使用该令牌获取资源 } }
5.应用使用access_token 向服务端提供的接口来获取数据,例如获取当前登陆用户信息。
url:http://ucenter.example.com/user/getInfo?access_token=kIJAkUd31F58nGpknvoW3L4r3S21koNd9Lk0rfcn
6.服务端验证受权以后,将所需数据返回给应用。
Response: { response: - { code: 200, msg: "success" }, datas: - { user_id: 5, user_name: "ellermister", full_name: "E先生", headimg: "http://q.qlogo.cn/headimg_dl?bs=qq&dst_uin=11733685&spec=100", address: "北京市北四环西路58号理想国际大厦" } }
7.更新通讯令牌方式借用第3步的,参数 grant_type 更改成 refresh_token,去掉code、redirect_uri,返回结果中增长 refresh_token 项,以后再次获取资源将使用refresh_token做为新的令牌。
补充下本身所遇到的问题,在前端实现上,会将用户从C站点引导到S站点进行登陆并受权,随后返回C站点。
此过程当中比较合理也是常见的过程则是,在C站点上点击登陆,弹窗S站点,登录受权完毕后关闭S站点,C站点进行刷新便可登陆成功。该问题涉及JS跨域刷新父窗口,解决方案以下。
//C站点弹出JS window.open('http://ucenter.example.com/authorize?response_type=code& client_id=f2292b656df429d4&state=xyz&redirect_uri=http%3A%2F%2Fclient%2Eexample%2Ecom%2Fuser.php', '弹出层页面','top=100,left=400,width=800,height=600,toolbar=no,menubar=no,scrollbars=yes, resizable=yes,location=no,status=no'); //S站点受权登陆后,执行JS url = 'http://client.example.com/user.php?code=xxxx&state=xyz';//回调URL try{ window.parent.opener.location.reload(); window.parent.close(); }catch(e){ window.parent.opener.location = url; window.parent.opener = null; window.parent.close(); }
从登陆到受权到获取资源基本流程也便是如此,期间的access_token 是针对用户的,固然也能够是针对全局的,相似于微信的作法。根据实际项目状况,作法不可能一成不变,本文只是提供了一个基本的流程,让初次了解的人可以快速的了解这个受权过程。
本文仅供参考理解,若有错误,请指正!
延伸阅读:
The OAuth 2.0 Authorization Framework https://tools.ietf.org/html/rfc6749
理解OAuth 2.0 http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html
OAuth 2 开发人员指南 http://www.oschina.net/translate/oauth-2-developers-guide