前言
现在很多的项目都需要接入第三方登录,比如 Google / Apple / Facebook 等登录方式,尤其是出海项目,因此很有必要把接入第三方登录的完整流程研究一遍。
接入第三方登录,核心就两个步骤:1. 按照官方提供的文档进行一些必要的配置(文档一般都会提供比较详细的接入步骤) 2. 开发人员编写代码实现效果;这里以 Google 登录为例进行讲解。
开发者平台配置
首先在 Google 开发者平台 进行配置,如果没有项目的话需要新建,这块的流程搞定之后,接下来进入 凭证 页面,生成客户端 ID 用于后续的登录流程,主要的操作步骤如下:
大家可以参考文档进行操作,这里不再详细展开。实现 Google 登录主要有两种模式,分别为弹出式窗口模式、重定向模式。
弹出式窗口模式
顾名思义,在原页面弹出一个小窗口进行登录,如下图所示:
这种模式下我们需要有一个按钮,当点击的时候唤起弹窗,Google 官方允许开发者自定义登录按钮,要显示 使用 Google 账号登录
按钮,可以选择 HTML 或者 JavaScript 的方式去实现,具体可以参考该链接:developers.google.com/identity/gs…
1.使用 HTML 呈现登录按钮,并将 JWT 返回给平台的登录端点:
<html>
<body>
<script src="https://accounts.google.com/gsi/client" async></script>
<div id="g_id_onload"
data-client_id="YOUR_GOOGLE_CLIENT_ID"
data-login_uri="https://your.domain/your_login_endpoint"
data-auto_prompt="false">
</div>
<div class="g_id_signin"
data-type="standard"
data-size="large"
data-theme="outline"
data-text="sign_in_with"
data-shape="rectangular"
data-logo_alignment="left">
</div>
<body>
</html>
2.使用 JavaScript 呈现登录按钮,并将 JWT 返回给 浏览器的 JavaScript 回调处理程序:
<html>
<body>
<script src="https://accounts.google.com/gsi/client" async></script>
<script>
function handleCredentialResponse(response) {
console.log("Encoded JWT ID token: " + response.credential);
}
window.onload = function () {
google.accounts.id.initialize({
client_id: "YOUR_GOOGLE_CLIENT_ID"
callback: handleCredentialResponse
});
google.accounts.id.renderButton(
document.getElementById("buttonDiv"),
{ theme: "outline", size: "large" } // customization attributes
);
google.accounts.id.prompt(); // also display the One Tap dialog
}
</script>
<div id="buttonDiv"></div>
</body>
</html>
这两段代码是在 Google 开发者平台中生成的,位于 生成集成代码 页面:
自定义按钮的样式以及模式:
我们只需要将生成的代码复制到项目中使用即可:
具体的实现代码:
<!-- 登录按钮 -->
<div class="oauth-item" style="display: none">
<div id="g_id_onload" :data-client_id="clientId" data-callback="googleCallback"</div>
<div
class="g_id_signin"
data-theme="outline"
:data-width="300"
:data-locale="en"
></div>
</div>
动态引入 JS 并绑定 Google 回调事件:
const clientId = ref('');
const createGoogleClient = () => {
const cb = (data: any) => {
const params = {
accessToken: data.credential,
...
};
loginByGoogleIdentity(params).then(res => {
if (res) {
// 登录成功后做一些处理
}
});
};
(window as any).googleCallback = cb;
const s = document.createElement('script');
s.src = 'https://accounts.google.com/gsi/client';
document.body.appendChild(s);
};
onMounted(() => {
createGoogleClient();
});
架构图
这里着重讲解重定向模式,官方文档也有提到,这种方式是最安全的,在该模式下,集成 Google 第三方登录的完整架构图如下:
重定向模式
在页面上准备一个按钮,点击跳转至 Google 授权登录页,跳转链接需要 client_id 以及 redirect_uri 两个字段的值。
// 重定向地址
const redirectUrl = `${location.origin}/loading`;
// Google授权登录页
const targetUrl = `https://accounts.google.com/o/oauth2/v2/auth?client_id=
${clientId}&scope=https://www.googleapis.com/auth/userinfo.email
&response_type=token&redirect_uri=${redirectUrl}`;
const signUpByGoogle = () => {
// 缓存当前页面的URL并进行跳转
sessionStorage.setItem('ThirdLoginUrl', location.href);
location.href = targetUrl
};
授权登录页如下:
在 Google 授权页面完成登录之后,接下来需要携带 token 返回重定向页面。
对于重定向页面,可以考虑把所有的第三方登录都重定向到同一个页面,便于管理,比如重定向至 loading 页面:
<template>
<div>Loading Page</div>
</template>
<script setup lang="ts">
import { handleThirdLogin } from '@/utils/third-login';
handleThirdLogin()
</script>
重定向页面 handleThirdLogin 方法的主要逻辑:从 URL 中获取 access_token,接着调用一遍我们系统内部的登陆接口,携带凭证 accessToken 传给后端,后端需要向 Google 服务器验证 token 的有效性,验证通过后将用户信息返回给前端:
function handleThirdLogin(cb?: () => void) {
const accessToken = takeAccessToken();
if (accessToken) {
return loginByGoogleAccount({
accessToken: accessToken,
...
})
.then(res => {
return loginCallback(res, cb);
})
}
return Promise.reslove(undefined)
}
function takeAccessToken() {
let accessToken = getValueFormHash(location?.hash?.split('&') || [], '#access_token=');
if (!accessToken) {
accessToken = getValueFormHash(location?.hash?.split('&') || [], '#/access_token=');
}
return accessToken
}
export const getValueFormHash = (hashArr: string[], key: string) => {
return hashArr.find((e: string) => e.startsWith(key))?.split(key)?.[1];
};
此时整个登录流程已经完成,执行 loginCallback 方法,前端可以跳转至原来的页面或者其他页面,还可以根据自己项目的需求增加一些处理逻辑:
const loginCallback = (res: any, cb?: () => void) => {
if (res) {
cb && cb();
const targetUrl = sessionStorage.getItem('ThirdLoginUrl');
sessionStorage.removeItem('ThirdLoginUrl')
location.href = targetUrl;
}
};