鉴于 spring security 与 oauth2.0标准过于繁琐,为方便理解与实际实现,故手写其实现。
1、oauth2.0介绍
以下部分参考自理解OAuth 2.0 - 阮一峰的网络日志
OAuth 2.0的运行流程如下图,摘自RFC 6749。
(A)用户打开客户端以后,客户端要求用户给予授权。
(B)用户同意给予客户端授权。
(C)客户端使用上一步获得的授权,向认证服务器申请令牌。
(D)认证服务器对客户端进行认证以后,确认无误,同意发放令牌。
(E)客户端使用令牌,向资源服务器申请获取资源。
(F)资源服务器确认令牌无误,同意向客户端开放资源。
1.1、oauth2.0-授权码模式介绍
(A)用户访问客户端,后者将前者导向认证服务器。
(B)用户选择是否给予客户端授权。
(C)假设用户给予授权,认证服务器将用户导向客户端事先指定的"重定向URI"(redirection URI),同时附上一个临时授权码。
(D)客户端收到临时授权码,附上早先的"重定向URI",向认证服务器申请令牌。这一步是在客户端的后台的服务器上完成的,对用户不可见。
(E)认证服务器核对了临时授权码和重定向URI,确认无误后,向客户端发送访问令牌(access token)。
1.2、oauth2.0-密码模式介绍
(A)用户向客户端提供用户名和密码。
(B)客户端将用户名和密码发给认证服务器,向后者请求令牌。
(C)认证服务器确认无误后,向客户端提供访问令牌。
2、逻辑交互
以某个**监测服务(web端)**为例,尝试登陆公司的SSO账户。
2.1、授权码模式实现
2.1.1、表结构设计
DROP TABLE IF EXISTS `sys_oauth_client`;
CREATE TABLE `sys_oauth_client` (
`client_id` varchar(50) NOT NULL COMMENT '客户端id',
`client_name` varchar(256) DEFAULT NULL COMMENT '应用名',
`client_secret` varchar(256) DEFAULT NULL COMMENT '应用密钥',
`client_redirect_uri_host` varchar(256) DEFAULT NULL COMMENT '对应主机域名',
`status` int(1) DEFAULT NULL COMMENT '状态。0:正常;1:冻结',
`create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
PRIMARY KEY (`client_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='应用client表';
BEGIN;
INSERT INTO `sys_oauth_client` VALUES ('joe_monitoring', '监测服务(web端)', 'admin123', '','0', '2018-08-30 19:42:32', '2018-08-30 20:24:08');
COMMIT;
DROP TABLE IF EXISTS `sys_oauth_user_authorize`;
CREATE TABLE `sys_oauth_user_authorize` (
`id` int(20) NOT NULL AUTO_INCREMENT,
`client_id` varchar(50) NOT NULL COMMENT '客户端id',
`oauth_user_id` varchar(50) NOT NULL COMMENT '绑定账号的id,例如对应wx来说,就是openId',
`user_id` int(11) NOT NULL COMMENT '对应user_id',
`oauth_user_name` varchar(50) DEFAULT '' COMMENT '绑定账号的名称',
UNIQUE KEY `Unique_client_idAnduser_id` (`client_id`,`user_id`) USING BTREE,
UNIQUE KEY `Unique_client_idAndoauth_user_id` (`client_id`,`oauth_user_id`) USING BTREE,
KEY `id` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='账号授权表'
技术选型上:springcloud
全家桶
2.1.2、各个服务
- 认证与授权服务:
joe-sso
,即SSO服务 - 用户、权限管理服务:
joe-admin
- api资源服务:
joe-resource
- 静态文本资源(css、js、img等)服务:
joe-file
- 监测服务:
joe-client-monitoring
另:
- 注册中心:
joe-eureka
- 网关:
joe-zuul
2.1.3、名词解释
- 客户端:这里指代 监测服务(web端); 其对应的
client_id
,在库中为joe_monitoring
- 认证服务器: 这里指代
joe-oauth
服务 - 重定向URI: 即下文中的
redirect_uri
字段 - 临时授权码:即下文中的
temp_authorize_code
字段 - 访问令牌:即下文中的
accessToken
字段
3、接口请求
3.1、监测服务(web端)
获取自身的client_id
GET
请求,接口: joe-client-wechat/client/clientId
返回示例:
{
"status": 0,
"msg": "成功。",
"data": "joe_monitoring"
}
3.2、前端跳转到登陆
页面,并传递过来参数
- 传递参数(以web浏览器为例,将其以get传参的形式,暴露在地址栏中):
参数 | 示例 | 说明 |
---|---|---|
client_id | joe_monitoring | 客户端id |
redirect_uri | http://localhost:9000/callback | 需重定向的uri |
eg:实际传递请求uri
解释:
passport.joe.com
:joe-sso
服务地址/authorize/login
: 授权码模式登陆接口
正常情况下,oauth2.0模式下,授权码模式,字段释义:
response_type:表示授权类型,必选项,此处的值固定为"code"
client_id:表示客户端的ID,必选项
redirect_uri:表示重定向URI,可选项
scope:表示申请的权限范围,可选项
state:表示客户端的当前状态,可以指定任意值,认证服务器会原封不动地返回这个值。
eg,在新浪微博中,采用360账号登陆,跳转的url为:
分析:上述uri,显然为oauth2.0模式下的授权码登陆模式。
3.3、登陆
页面的登陆
GET
请求,接口: joe-sso/oauth/authorize/login
即 SSO账户系统的登陆系统下的“登陆”按钮
- 传递参数:
参数 | 示例 | 说明 |
---|---|---|
client_id | joe_monitoring | 客户端id |
redirect_uri | http://localhost:9000/callback | 需重定向的uri |
login_name | admin | 用户名 |
password | admin | 密码 |
- 若登陆成功,返回
temp_authorize_code
临时授权码 :
{
"status": 0,
"msg": "成功。该临时授权码有效期为10分钟",
"data": {
"temp_authorize_code": "joe_monitoring:28115fff54884bc0800442ffb51a3f98"
}
}
- 其他错误情况:
{
"status": 20,
"msg": "失败。joe-sso认证失败,请检查client_id的准确性,或该client_id已被冻结",
"data": null
}
{
"status": 30,
"msg": "失败。登陆名或密码错误,用户不存在",
"data": null
}
获取到 临时授权码temp_authorize_code
后,登陆
页面前端跳转链接回监测服务(web端)
(通过redirect_uri)
注意:该temp_authorize_code
存储在redis
中,设置好过期时间。
3.4、监测服务(web端)
尝试向joe_sso
获取accessToken
-
上一步,我们获取到
temp_authorize_code
。现在,将temp_authorize_code
发送给SSO服务,从而获取accessToken。 -
监测服务(web端)
前端通过跳转回的浏览器地址栏,获取到对应到temp_authorize_code
后,发送GET
请求到监测服务(web端)
的后端,接口:joe-client-monitoring/client/accessToken
-
监测服务(web端)
的后端,将会添加自己的client_id
、client_secret
参数,一起通过http
发送给joe-sso账户服务
,获取accessToken。
对应接口joe-sso账户服务
的获取accessToken接口为:http://passport.joe.com/authorize/accessToken
参数 | 示例 | 说明 |
---|---|---|
client_id | joe_monitoring | 客户端id |
client_secret | admin123 | 客户端密钥 |
redirect_uri | http://localhost:9000/callback | 需重定向的uri,和上一步骤一致 |
temp_authorize_code | joe_monitoring:52d1595aeba64b4fa087c9c96ab42bb2 | 临时授权码 |
eg:实际传递请求uri
返回结果:
{
"status":0,
"msg":"成功。该access_token有效期为120分钟,下次请求将重置有效期",
"data":"joe_monitoring:d29d492c678640a6b3952c4fb0dc24be"
}
该accessToken有效期120分钟,后续每次请求会重置有效期(类似session的功能)。后期考虑添加:超时1天后,强制失效重新登陆的功能。
注意:
- 该
获取accessToken
存储在redis
中,设置好过期时间。 监测服务(web端)
需要将对应的 accessToken 自行存储起来(jvm cache或 redis中均可)。
注意:该请求过程,对于用户来说,是无感知的。
至此,授权服务已基本完成。
3.5、微信客户端
获取用户信息 by accessToken
GET
请求,接口: joe-sso/oauth/userInfo
- 传递参数:
http
的header
中添加joe_access_token
,即
参数 | 示例 | 说明 |
---|---|---|
joe_access_token | joe_monitoring:52d1595aeba64b4fa087c9c96ab42bb2 | 即第3.4步骤中,微信客户端 尝试获取的accessToken |
返回结果:
{
"status": 0,
"msg": "成功。",
"data": {
"create_time": "2018-09-11 10:16:12",
"user_id": 1,
"user_name": "admin",
"user_nick_name": "我是用户昵称joe",
"redirect_uri": "http://localhost:9000/callback"
}
}
其他
源码
文中所述功能均已实现。具体代码,后期将视情况发布至github
上。地址为:github仓库地址