前端接入 Google OAuth 2.0 三方授权登录

因海外业务拓展,项目需要支持 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'

可以看到是由三部分拼接而成,对应我们第一步中选中的 emailprofileopenid

所以,这就是我为什么说文档里写的示例不管用的原因了,文档示例如下:

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,但其实也不能说它错,只能说它没有生效,因为这里面有一个增量授权的问题:

在登录时,应用可能会请求 openidprofile 范围来执行基本登录,然后在第一次请求保存时混合请求 https://www.googleapis.com/auth/drive.file 范围。

所以,在我理解授权登录还需要额外指定 userinfo.profileopenid,所以不能只填写文档示例中的链接,而应该填写 Playground 中给出的链接。但是 userinfo.profileopenid 额外的授权范围会最终追加到 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)、返回字段名称等,仅供参考。

三方 授权请求地址 请求参数 响应类型 成功响应 失败响应
Google accounts.google.com/o/oauth2/v2... client_id redirect_uri response_type: 'token' scope: 'www.googleapis.com/auth/userin...' hash 片段 access_token error
Facebook 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"
}

这一步,只能交给后端来处理,前端会有跨域问题。

此外,针对表格还有几点需要说明:

  1. 这里仅罗列必传参数,完整参数还请查阅参考链接中的官方文档;
  2. 有些三方返回方式是由参数 response_type 决定而非是固定的。比如 Facebook,如果 response_type=code,那么所包含的响应数据为网址参数形式(search),而不会是网址片段形式(hash);
  3. Twitter 我的项目中用的是 1.0,所以就不写入其中了,不过 2.0 的文档也给在了下面。

总结

本文以 Google OAuth 2.0 为例,给大家分享了前端不使用 SDK 时,接入三方授权的流程与步骤。其实步骤并不是很繁琐,善于查阅文档并通晓其使用流程就能正确接入三方服务了。

参考资料

相关推荐
亚里士多没有德7753 分钟前
强制删除了windows自带的edge浏览器,重装不了怎么办【已解决】
前端·edge
micro2010147 分钟前
Microsoft Edge 离线安装包制作或获取方法和下载地址分享
前端·edge
.生产的驴11 分钟前
Electron Vue框架环境搭建 Vue3环境搭建
java·前端·vue.js·spring boot·后端·electron·ecmascript
awonw14 分钟前
[前端][easyui]easyui select 默认值
前端·javascript·easyui
九圣残炎35 分钟前
【Vue】vue-admin-template项目搭建
前端·vue.js·arcgis
柏箱1 小时前
使用JavaScript写一个网页端的四则运算器
前端·javascript·css
TU^1 小时前
C语言习题~day16
c语言·前端·算法
学习使我快乐014 小时前
JS进阶 3——深入面向对象、原型
开发语言·前端·javascript
bobostudio19954 小时前
TypeScript 设计模式之【策略模式】
前端·javascript·设计模式·typescript·策略模式