因海外业务拓展,项目需要支持 Google、Facebook、Twitter、Line、Apple 这些第三方授权登录。目前第三方的授权登录有很多方案,且都有官方提供 SDK 方便接入。但由于我这个项目需要同时支持H5、iOS、Android,所以选择在不接入官方提供的 SDK 的情况下,配合后端为 Web 网页实施基于浏览器的授权登录。
本文围绕 Google OAuth 2.0 展开。当然,不同厂商的流程有些许区别,文章也会有所提及。
OAuth 2.0 简介
OAuth 即 Open Authorization,是"开放授权"的简写,它是一个关于授权(authorization)的开放网络标准。一句话,OAuth2.0 是一种授权协议。
无论使用哪种三方平台,最重要的一步就是获取三方授权的 token
,当然不同的厂商叫法不一样,有的叫 access_token
,有的叫 oauth_token
,还有的叫 code
,无所吊谓。关键是拿到这个凭证后,你就有权限继续去三方获取用户的昵称、头像、动态、好友列表之类的信息。当然,权限也是有范围和有效期的。
对 OAuth 2.0 感兴趣的,可以看一些社区相关文章:
前端接入 Google
首先,我们要去谷歌开发者备案,创建项目并按提示填写信息。其中有两步是开发过程中会用到的,一个是 创建 OAuth 客户端 ID
,该步骤会生成 client_id
。还有一个是 已获授权的重定向 URI
,该步骤能在授权成功后,携带 token
重定向你设置的 URI 上。
具体后台配置推荐直接看这篇文章 👉 谷歌OAuth2.0开发的正确配置步骤
Google Web 端应用流程
整个 OAuth 的授权登录流程如下:
我们着重看下和 OAuth 相关的第二、三两步(其他几步都是后端给出 api 接口,前端调用即可)。
Google OAuth 2.0 参数
在访问 Google 授权登录页之前,需要准备以下必传网址参数。
参数 | 是否必传 | 说明 |
---|---|---|
client_id | Y | 客户端 ID |
redirect_uri | Y | 完成授权后,API 服务器会将用户重定向到该地址 |
response_type | Y | JS 设为 'token' |
scope | Y | 授权范围 |
state | N(推荐) | 维护授权请求和授权服务器状态的任何字符串值,防止 CSRF 攻击 |
response_type
:设为 'token' 的意思是,授权后的 token 令牌将以 access_token=abcxxx
键值对的格式返回,并且会以 fragments 网址片段(即 location.hash,不是 location.search)的形式拼接到重定向的 URI 后面。
scope
:这个授权范围比较重要,令人吐槽的是官方文档说了一大堆,示例里还写错了(不管用),我们这里应该填写 https://www.googleapis.com/auth/userinfo.profile
。至于我是怎么找到的,可以看下面[OAuth 2.0 Playground](#OAuth 2.0 Playground "#OAuth-2.0-Playground")的演示。
state
:防止 redirect_uri
被别人猜到,确保请求和响应源自同一浏览器,从而防范 CSRF 跨站请求伪造等攻击。前端可以使用 uuid(通用唯一识别码)来生成 state。
其他可选参数,详见 👉 获取 OAuth 2.0 访问令牌
OAuth 2.0 Playground
如需查看关于将 OAuth 2.0 与 Google 搭配使用(包括使用您自己的客户端凭据的选项)的交互式演示,请试用 OAuth 2.0 Playground。
官网给了个 Playground 演练场,我们可以在里面找到对应的授权服务,模拟授权登录场景,Google 会显示出对应的响应信息,方便我们进行调试,这一点开始很贴心的。
step 1 选择授权 API
选择 Google OAuth 2 API V2,并按需要勾选授权范围,我们这里全部勾选,并点击 Authorize APIs
看看会发生什么。
随后,会拉起 Google 登录页面,我们登录即可。
step 2 生成 access_token
调用 API 后,会来到第二步,右边会显示刚刚的请求响应信息。主要看下 Request
,会发现其中的 scope
,我们解析出来看看是什么:
js
decodeURIComponent('scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile+openid')
// outout:
'scope=https://www.googleapis.com/auth/userinfo.email+https://www.googleapis.com/auth/userinfo.profile+openid'
可以看到是由三部分拼接而成,对应我们第一步中选中的 email
、profile
和 openid
。
所以,这就是我为什么说文档里写的示例不管用的原因了,文档示例如下:
js
https://accounts.google.com/o/oauth2/v2/auth?
scope=https%3A//www.googleapis.com/auth/drive.metadata.readonly&
include_granted_scopes=true&
response_type=token&
state=state_parameter_passthrough_value&
redirect_uri=https%3A//oauth2.example.com/code&
client_id=client_id
它给的是 https://www.googleapis.com/auth/drive.metadata.readonly
,但其实也不能说它错,只能说它没有生效,因为这里面有一个增量授权的问题:
在登录时,应用可能会请求
openid
和profile
范围来执行基本登录,然后在第一次请求保存时混合请求https://www.googleapis.com/auth/drive.file
范围。
所以,在我理解授权登录还需要额外指定 userinfo.profile
和 openid
,所以不能只填写文档示例中的链接,而应该填写 Playground 中给出的链接。但是 userinfo.profile
和 openid
额外的授权范围会最终追加到 auth/drive.file
中。
最后,我们点击 exchange authorization code for tokens
,Playground 就会给我们生成 access_token
以及其他信息。
模拟结束,其实到这里就已经成功了。
哈希片段获取access_token
从 Google 那边授权成功后,会返回到前端指定的 redirect_uri
页面中,我们可以从 URL 地址的哈希片段(注意是 hash
,不是 search
)中拿到 access_token
,并传给后端。
js
// utils:获取 location.search/hash 中的值
const searchParams = (search, ...keys) => {
search = search.replace(/#|\?/, '');
const params = new URLSearchParams(search);
return flatMap(keys).reduce((prev, key) => {
prev[key] = params.get(key) || undefined;
return prev;
}, {});
};
// redirect_uri 页面
export default {
created() {
// 成功
if (/code|token/.test(this.$route.fullPath)) {
const tokenInfo = this.getAccessToken();
// 拿着 tokenInfo 请求后端接口······
}
},
methods: {
// 获取 access_token
getAccessToken() {
const { access_token: googleToken } = searchParams(
this.$route.hash,
'access_token'
);
return { googleToken };
}
}
}
错误处理
如果 Google 授权失败,那么根据文档说明,会携带 error
字段:
js
https://oauth2.example.com/callback#error=access_denied
js
// redirect_uri 页面
export default {
created() {
// 成功···见上
// 失败:
const { error } = searchParams(this.$route.hash, 'error');
if (error) {
// 处理失败······
}
}
}
Facebook、Line、Apple 配置大全
最后,我们来看看其他几个三方都需要哪些参数,主要区别在于参数名称、返回方式(hash 还是 search)、返回字段名称等,仅供参考。
三方 | 授权请求地址 | 请求参数 | 响应类型 | 成功响应 | 失败响应 |
---|---|---|---|---|---|
accounts.google.com/o/oauth2/v2... | client_id redirect_uri response_type : 'token' scope : 'www.googleapis.com/auth/userin...' |
hash 片段 | access_token | error | |
www.facebook.com/v17.0/dialo... | client_id redirect_uri response_type : 'token' |
hash 片段 | access_token | error_description | |
Apple | appleid.apple.com/auth/author... | client_id redirect_uri response_type : 'code id_token' response_mode : 'fragment' |
hash 片段 | code id_token | error |
Line | access.line.me/dialog/oaut... | client_id redirect_uri response_type : 'code' |
search 片段 | code | error_description errorMessage 等 |
其中,Line 有点特别。在获取到 code
后,还需要拿着 code
调用接口换取 access_token
:
bash
curl -v -X POST https://api.line.me/v2/oauth/accessToken \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'grant_type=authorization_code' \
-d 'code=b5fd32eacc791df' \
-d 'redirect_uri=https%3A%2F%2Fexample.com%2Fauth' \
-d 'client_id=12345' \
-d 'client_secret=d6524edacc8742aeedf98f'
json
{
"access_token": "bNl4YEFPI/hjFWhTqexp4MuEw5YPs7qhr6dJDXKwNPuLka...",
"expires_in": 2591977,
"refresh_token": "8iFFRdyxNVNLWYeteMMJ",
"scope": "P",
"token_type": "Bearer"
}
这一步,只能交给后端来处理,前端会有跨域问题。
此外,针对表格还有几点需要说明:
- 这里仅罗列必传参数,完整参数还请查阅参考链接中的官方文档;
- 有些三方返回方式是由参数
response_type
决定而非是固定的。比如 Facebook,如果 response_type=code,那么所包含的响应数据为网址参数形式(search),而不会是网址片段形式(hash); - Twitter 我的项目中用的是 1.0,所以就不写入其中了,不过 2.0 的文档也给在了下面。
总结
本文以 Google OAuth 2.0 为例,给大家分享了前端不使用 SDK 时,接入三方授权的流程与步骤。其实步骤并不是很繁琐,善于查阅文档并通晓其使用流程就能正确接入三方服务了。
参考资料
- Google Cloud
- Google | 适用于客户端 Web 应用的 OAuth 2.0
- OAuth 2.0 Playground
- Facebook | 手动构建登录流程
- Apple | Incorporating Sign in with Apple into other platforms
- Line | Authenticating users and making authorization requests
- Twitter | OAuth 2.0
- 谷歌OAuth2.0开发的正确配置步骤
- 接入谷歌OAuth2.0登录的分析和代码实践
- 实现 Google 第三方授权登录
- 实现 Facebook 第三方授权登录