视频回放: https://www.bilibili.com/video/av13407177/20
本期内容简介 00:00
OAuth 简介 01:34
OAuth(开放授权)是一个开放标准,允许用户让第三方应用访问该用户在某一网站上存储的私密的资源(如照片,视频,联系人列表),而无需将用户名和密码提供给第三方应用。维基百科1
提示:需要用户在别的网站上的私密信息时,直接向用户要账号密码有点太不专业了。
以 Ticket 为例,转换下说法就是:
允许用户让 Ticket 应用访问该用户在 LeanCloud 上存储的私密资源(如账号,应用列表),而无需将用户名和密码提供给 Ticket。
使用 Ticket 为例,介绍 OAuth 授权过程 04:20
预备工作
- Ticket 向 LeanCloud 申请 OAuth 接入。
- LeanCloud 审核通过后,给 Ticket 颁发
clientId
和 clientSecret
。
用户申请授权 06:15
用户访问 Ticket 应用,当 Ticket 需要用户在 LeanCloud 上的信息时(比如 Ticket 想使用用户在 LeanCloud 上的信息来注册自己的网站),OAuth 授权过程开始。
-
Ticket 服务端拼接 LeanCloud 的授权地址给用户,并附带一些信息,包括:
- clientId:准备工作里面 LeanCloud 办法给 Ticket 的 clientId。
- scope:Ticket 希望获取用户在 LeanCloud 上信息的范围,比如只要基本账户信息,或者再加上应用列表信息等。注意:不是要的信息越多越好,用户可能因为应用需要的信息过多而放弃授权。
- callbackUrl:当用户确认授权之后,将会跳转回来的页面。
-
用户浏览器根据刚才的响应,跳转到 LeanCloud 的授权页面。LeanCloud 授权页面会展现一些信息:
用户根据这些信息判断是否同意授权:如果取消,OAuth 授权流程终止;如果同意,LeanCloud 会生成一个重定向请求到 Ticket,并在请求上附带一个属性 code
。该 code
在 LeanCloud 内部会记录,并且和 Ticket 应用,还有当前授权用户关联。至于调转到 Ticket 的哪个地址,由刚才 Ticket 重定向过来时候的 callbackUrl
决定。
- 用户浏览器根据重定向请求继续访问 Ticket 的相关页面,并携带了
code
信息。Ticket 服务端会向 LeanCloud 发起请求,获取用户的 accessToken
信息,请求会携带一些参数:
LeanCloud 根据参数信息就能确定是哪个应用要获取哪个用户的 accessToken
,如果所有信息确认无误,则返回 accessToken
。
提示:clientSecret
是私密的,不能泄漏,建议不要直接配置在项目代码中。如果部署在云引擎,建议使用云引擎的环境变量来保存这些数据。云引擎环境变量设置方式: LeanCloud 控制台 -> 云引擎 -> 设置 -> 自定义环境变量,增加需要的变量名和变量值,点保存,下次部署时生效。
- 当 Ticket 服务端拿到用户的
accessToken
之后,就可以通过该信息作为凭证去请求这用户的信息(当然需要在 scope
的范围内)。
至此,OAuth 授权结束。
相关代码介绍 25:26
本期视频使用的代码地址:https://github.com/leancloud/ticket
版本:664259e
可以使用下面的命令获取:
git clone https://github.com/leancloud/ticket.git
cd ticket
git checkout 664259e
整个流程涉及到的代码部分:
- 用户登录页关于 OAuth 授权的代码 modules/Login.js7:
<div className={css.wrap}>
<h1 className='font-logo'>欢迎回来</h1>
<hr />
<p>目前只支持通过 {ORG_NAME} OAuth 授权进行登录</p>
<a href='/oauth/login' className='btn btn-primary'>前往 {ORG_NAME} 授权页</a>
</div>
可见,当用户点击「前往 LeanCloud 授权页」按钮是,将请求 GET /oauth/login
路由。
- 服务端的路由定义在 api/index.js4:
...
router.use('/oauth/login', require('./oauth').login(loginCallbackUrl))
...
路由方法定义在 api/oauth.js 的 login 方法中9:
exports.login = (callbackUrl) => {
return (req, res) => {
const loginUrl = serverDomain + '/1.1/authorize?' +
qs.stringify({
client_id: config.oauthKey,
response_type: 'code',
redirect_uri: callbackUrl,
scope: oauthScope,
})
res.redirect(loginUrl)
}
}
该方法接受一个 callbackUrl
参数,返回一个 express 的路由方法(即接收 request
和 response
为参数的方法)。在这个路由方法中拼接了 LeanCloud 授权页的请求,然后向客户端回复一个 302 响应(由 res.redirect()
方法实现)。
- 用户浏览器跳转到 LeanCloud 授权页,授权成功后,用户浏览器重定向到 Ticket 的 相关路由定义2:
...
router.use(loginCallbackPath, require('./oauth').loginCallback(loginCallbackUrl))
...
路由的 具体实现3:
exports.loginCallback = (callbackUrl) => {
return (req, res) => {
getAccessToken(req.query.code, callbackUrl).then((accessToken) => {
accessToken.uid = '' + accessToken.uid
return AV.User.signUpOrlogInWithAuthData(accessToken, 'leancloud')
}).then((user) => {
if (_.isEqual(user.createdAt, user.updatedAt)) {
// 第一次登录,从 LeanCloud 初始化用户信息
return initUserInfo(user)
}
return user
}).then((user) => {
res.redirect('/login?token=' + user._sessionToken)
})
}
}
可见路由的实现中:
- 先使用请求携带的
code
信息到 LeanCloud 网站获取该用户的 accessToken
(详见 getAccessToken1)。
- 使用 JS SDK 的 AV.User.signUpOrlogInWithAuthData()3 的方法来注册或者登陆用户(根据是否有相同授权信息的用户决定)。这样注册的用户,账号和密码都是随机生成,
_User
表的 authData
列会保存用户在对应平台的授权信息。
- 如果用户是第一次登陆,则使用该用户在 LeanCloud 上的
username
和 email
信息来设置用户在 Ticket 里的信息(详见 initUserInfo3)。
- 生成 302 响应,并将用户的
sessionToken
作为 queryString
,进行客户端登录(res.redirect('/login?token=' + user._sessionToken)
)。客户端收到服务端响应后,使用 sessionToken
进行客户端登录,代码 modules/Login.js2
const query = nextProps.location.query
if (query.token) {
return AV.User.become(query.token)
...
}
至此,OAuth 授权,以及服务端创建(或登录)用户,以及客户端同步登录过程全部结束。
如果需要用户在 LeanCloud 上的其他信息,Ticket 服务端可以使用用户的 accessToken
(_User
表的authData
属性中)来获取,可以参考这些方法:
其他说明 45:08
我们只介绍了 OAuth 的最基本流程,以及最必要的请求参数,至于 OAuth 授权为何需要这些步骤,以及每一步的某些参数为何要这样设计,建议大家参考更详细的文档或协议说明。
如果自己的应用要申请别的网站 OAuth 授权(比如 QQ),可以查看对应网站提供的 OAuth 相关文档。
如果要自己实现 OAuth Server,不需要自己实现,各个语言都有相关的库,能极大的降低实现成本。