关于 OAuth2 的介绍可以参考
http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html
http://www.ruanyifeng.com/blog/2019/04/oauth_design.html
http://www.ruanyifeng.com/blog/2019/04/oauth-grant-types.html
在这些文章中,使用 client_id,本文使用 app_id,这两者可以认为一致。
表结构设计
授权系统流程
OAuth 平台开放场景:
1. 系统积累了大量用户,需要开放用户信息给第三方应用共享。
2. 场景采用无状态服务,首先客户端发送 username / password 到服务端认证,认证通过后,服务端将用户信息加密并编码成一个 token,返回给客户端,比如:JWT。
3. 以后客户端每次发送请求,都需要携带 token;服务端对客户端发送来的 token 进行解密,判断是否有效,并获取用户信息
A 网站:授权方;B 网站:第三方。
1. A 网站提供一个链接,用户点击后就会跳转到 B 网站,授权用户数据给 A 网站使用。
https://b.com/oauth/authorize?
response_type=code&
client_id=CLIENT_ID&
redirect_uri=CALLBACK_URL&
scope=read
伪代码
/* 判断 appid 是否为空 */
if (app_id 参数不存在) {
// message: app_id 参数不存在
}
/* 判断 appid 是否存在于数据库 */
if (app_id 不存在于数据库) {
// message: app_id 无效,请先注册
}
/* 判断 response_type 是否存在 */
if (response_type 参数不存在) {
// message: response_type 参数不存在
}
/* 如果只支持授权码模式,判断 response_type 值是否为 code */
if (response_type 值不为 code) {
// message: 目前只支持授权码 (code) 模式
}
/* 判断是否存在 redirect_url */
if (redirect_url 参数不存在) {
// message: redirect_url 参数不存在
}
/* 判断 redirect_url 是否是可信域名 */
if (redirect_url 来自不可信任的域名) {
// message: 请先添加可信域名
}
/* 判断是否存在 scope */
if (scope 参数不存在) {
// message: scope 参数不存在
}
/* 判断 scope 是否包含授权域 openapi_login */
if (scope 授权域是否包含 openapi_login) {
// scope 授权域不正确
}
$query_string = "appid={$app_id}";
$query_string .= "&redirect_url={$redirect_url}";
$query_string .= "&response_type={$response_type}";
$query_string .= "&scope={$scope}";
$query_string .= "&state={$state}";
redirect("http://cannedbread.com/?{$query_string}");
2. 用户跳转后,B 网站会要求用户登录,然后询问是否同意给予 A 网站授权。用户表示同意,这时 B 网站就会跳回redirect_uri
参数指定的网址。
由于目前鉴权模式是无状态的 token 验证,因此统一跳转到前端的指定地址,然后由前端判断是否处于登录状态(session storage 等),如果登录就展示授权的确认与取消,如果未登录就需要先登录。
如果同意授权,则会跳转到 direct_url,并携带 code。
https://a.com/callback?code=AUTHORIZATION_CODE
3. A 网站拿到授权码以后,就可以在后端,向 B 网站请求令牌。
https://b.com/oauth/access_token?
client_id=CLIENT_ID&
client_secret=CLIENT_SECRET&
grant_type=authorization_code&
code=AUTHORIZATION_CODE&
redirect_uri=CALLBACK_URL
伪代码
<?php
/* 判断 appid 是否为空 */
if (app_id 参数不存在) {
// response: app_id 参数不存在
}
/* 判断 appid 是否存在于数据库 */
if (app_id 不存在于数据库) {
// response: app_id 无效,请先注册
}
/* 判断 app_secret 是否存在 */
if (app_secret 参数不存在) {
// response: app_secret 参数不存在
}
/* 检查 app_id, app_secret 合法性 */
if (app_id 和 app_secret 不匹配) {
// response: app_secret 错误
}
/* 判断 grant_type 是否存在 */
if (grant_type 参数不存在) {
// response: grant_type 参数不存在
}
/* 如果只支持 authorization_code,判断授权类型是否为 authorization_code */
if (grant_type 值不是 authorization_code) {
// response: 仅支持 authorization_code 授权类型
}
/* 判断 code 是否存在 */
if (code 参数不存在) {
// response: code 参数不存在
}
/* 判断 code 是否有效 */
if (code 无效) {
// response: code 无效
}
/* 判断 code 是否被使用 */
if (code 被使用) {
// response: code 已经被使用
}
/* 判断 code 是否过期 */
if (code 已经过期)) {
// response: code 已经过期
}
/* 判断用户的 token 信息是否存在缓存 */
if (access_token 不存在) {
// 1. 生成新的 access_token、refresh_token
// 2. response
}
/* 数据库已经存在缓存 */
/* 判断 access_token 是否过期 */
if (access_token 没有过期) {
// 1. 刷新 access_token、refresh_token 时间(延长时间)
// 2. response
}
/* access_token 过期 */
// 1. 生成新的 access_token、refresh_token
// 2. 更新缓存
// 3. response
未完待续