2019独角兽企业重金招聘Python工程师标准>>>
当你运营越来越多的项目,每个项目的业务都不一样,每个需要使用到这些业务的用户就需要注册方能进行使用。如果用户还需要使用其他项目的功能,就必须还得注册使用。造成反复注册,反复登录的不友好体验。
此时很多人都会想着使用ucenter等方式来整合用户,将用户放到一个数据中心来管理。所有的项目都将使用这一个公共的数据中心来运行。ucenter和各个项目之间通信将采用隐式api的方式进行通信,所有的行为都在后端进行,好比你的淘宝账号可以在天猫登录一样。在这里,假设你也做了一个业务网站,你的网站用户需要一键分享到他的淘宝主页中、或者需要读取卖家的商品信息,那么总不能在你的网站让用户提供他的淘宝账号密码,这显得尤为的不专业和不安全。oauth2就刚好解决了这一问题,让用户使用淘宝的快捷登录进行授权后由淘宝提供的接口来返回用户相关信息,并将这一信息在自己数据库中生成对应的一份关联起来,这一切就变得比较安全,比较合理了。
本文的主题是oauth2,oauth是一种协议,2是版本,即oauth2是一个协议标准,将多个项目整合起来,提供所需的接口通信,oauth2可以建立在ucenter上可以建立在单独业务上,总之他只是一种协议,具体关于该协议的介绍请直接浏览官网或者百科介绍。
上文中有提到淘宝的快捷登录,那么对应就还有QQ快捷登录(QQ互联)、新浪快捷登录、google账号登录等。这样一来就可以使得原本来自QQ、来自google的用户无需在你的网站进行注册后,直接间接使用原先的媒体账号登录就可以在你的网站访问。用google举例,你的网站可以使用google账号快捷登录,并且可以获得google提供的一些接口服务,那么将这一关系中的google称为server端,你的网站称之为client端,下文将client称为应用。
当前主要是说自己构造一个oauth2 server端,就是上文中所提到的QQ互联、微博等,可以给自己的其他应用或者开放给第三方应用来提供方便用户管理的一个系统。
上图是Oauth2运行流程,描述了四个角色之间交互的方式。摘自RFC 6749
(A):Client 向 Resoure Owner 请求授权。可以理解为用户发起请求。
(B):在Server端用户同意给予 Client 端授权,并返回至 Client 端告知。
(C):Client携带Authorization Grant 向Authorization Server请求获取Access Token。
(D):授权验证通过后,返回Access Token。
(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