“使用 Google 帐号登录”
功能可快速管理网站上的用户身份验证。用户登录 Google 账号、表示同意,并安全地与平台共享其个人基础资料信息。
- 官方文档:链接
一、获取 Google API 客户端 ID
-
打开
Google API 控制台
中的凭据页面 -
创建或选择 Google API 项目。
-
点击创建凭据 > OAuth 客户端 ID,对于应用类型,选择 Web 应用以创建新的客户端 ID。
-
将您网站的 URI 添加到已获授权的 JavaScript 来源中。URI 仅包含架构和完全限定的主机名。例如
https://www.example.com
。
- 对于本地测试或开发,请同时添加
http://localhost
和http://localhost:<port_number>
- Google 一键快捷功能只能在
HTTPS
网域中显示。
(可选)
使用重定向到托管的端点(而不是通过 JavaScript 回调)返回凭据。在这种情况下,请将重定向 URI 添加到已获授权的重定向 URI 中。重定向 URI 包含 scheme、完全限定主机名和路径,并且必须符合重定向 URI 验证规则。例如https://www.example.com/auth-receiver
。
二、加载客户端库
在需要用到的页面上载入客户端库
<script src="https://accounts.google.com/gsi/client" async></script>
由于,我们需要在react项目中引用,后面会有个简单的组件来实现调用
三、集成代码
HTML版
官方提供了在线HTML集成代码生成器:链接
- 《“使用 Google 帐号登录”HTML API 参考文档》
生成代码如下(仅供参考):
<div id="g_id_onload"
data-client_id="testid"
data-context="signin"
data-ux_mode="popup"
data-login_uri="http://localhost/login"
data-auto_prompt="false">
</div>
<div class="g_id_signin"
data-type="standard"
data-shape="rectangular"
data-theme="outline"
data-text="signin_with"
data-size="large"
data-logo_alignment="left">
</div>
支持的数据属性:
属性 | 说明 |
---|---|
data-client_id | 您的应用的客户端 ID |
data-auto_prompt | 显示 Google One 点按信息。 |
data-auto_select | 为 Google 一键启用自动选择功能。 |
data-login_uri | 登录端点的网址 |
data-callback | JavaScript ID 令牌处理程序函数名称 |
data-native_login_uri | 密码凭据处理程序端点的网址 |
data-native_callback | JavaScript 密码凭据处理程序函数名称 |
data-native_id_param | credential.id 值的参数名称 |
data-native_password_param | credential.password 值的参数名称 |
data-cancel_on_tap_outside | 控制当用户在提示之外点击时是否取消提示。 |
data-prompt_parent_id | 一键式提示容器元素的 DOM ID |
data-skip_prompt_cookie | 如果指定的 Cookie 具有非空值,则跳过一次点按。 |
data-nonce | ID 令牌的随机字符串 |
data-context | 一键式提示中的标题和字词 |
data-moment_callback | 提示界面状态通知监听器的函数名称 |
data-state_cookie_domain | 如果您需要在父网域及其子网域中调用一键快捷功能,请将父网域传递给此属性,以便使用单个共享 Cookie。 |
data-ux_mode | “使用 Google 账号登录”按钮用户体验流程 |
data-allowed_parent_origin | 可以嵌入中间 iframe 的来源。如果存在此属性,则一键快捷功能会在中间 iframe 模式下运行。 |
data-intermediate_iframe_close_callback | 当用户手动关闭一键式按钮时,替换默认的中间 iframe 行为。 |
data-itp_support | 在 ITP 浏览器上启用升级后的一键式用户体验。 |
data-login_hint | 通过提供用户提示跳过账号选择。 |
data-hd | 按网域限制帐号选择。 |
data-use_fedcm_for_prompt | 允许浏览器控制用户登录提示并在您的网站和 Google 之间协调登录流程。 |
JavaScript版
《“使用 Google JavaScript API 参考文档》
初始化方法:google.accounts.id.initialize
google.accounts.id.initialize(IdConfiguration)
支持数据类型(IdConfiguration
):
属性 | 说明 |
---|---|
client_id | 您的应用的客户端 ID |
auto_select | 启用自动选择功能。 |
callback | 处理 ID 令牌的 JavaScript 函数。Google 一键快捷功能和“使用 Google 账号登录”按钮 popup 用户体验模式会使用此属性。 |
login_uri | 登录端点的网址。“使用 Google 账号登录”按钮 redirect 用户体验模式会使用此属性。 |
native_callback | 处理密码凭据的 JavaScript 函数。 |
cancel_on_tap_outside | 在用户点击提示之外的位置时取消提示。 |
prompt_parent_id | 一键式提示容器元素的 DOM ID |
nonce | ID 令牌的随机字符串 |
context | 一键式提示中的标题和字词 |
state_cookie_domain | 如果您需要在父网域及其子网域中调用一键快捷功能,请将父网域传递给此字段,以便使用单个共享 Cookie。 |
ux_mode | “使用 Google 账号登录”按钮用户体验流程 |
allowed_parent_origin | 可以嵌入中间 iframe 的来源。如果存在此字段,则会在中间 iframe 模式下运行一键快捷功能。 |
intermediate_iframe_close_callback | 当用户手动关闭一键式按钮时,替换默认的中间 iframe 行为。 |
itp_support | 在 ITP 浏览器上启用升级后的一键式用户体验。 |
login_hint | 通过提供用户提示跳过账号选择。 |
hd | 按网域限制帐号选择。 |
use_fedcm_for_prompt | 允许浏览器控制用户登录提示,并在您的网站和 Google 之间协调登录流程。 |
更多详细用法,可以查阅官方文档
四、react项目中集成
环境
- react: ^18
- react-i18next:^14.0.5
- next: 14.1.0
按钮语言根据浏览器当前语言自动切换
展示&调用部分
// file: ./component/Google/index.tsx
"use client";
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { AuthServerConfigs } from "@/configs/server.configs";
import { GoogleVerify } from "./GoogleServer";
import Logs from "@/utils/logs";
type GoogleCallbackProps = {
clientId?: string;
client_id?: string;
credential: string; // 返回的 ID 令牌
select_by: string; // 凭据的选择方式 @link https://developers.google.cn/identity/gsi/web/reference/js-reference?hl=zh-cn#select_by
};
let _alphaTabInstance: any = null;
// 载入或卸载Google库资源
function loadGoogleGsi(isLoad: boolean) {
const scriptId = "google-gsi";
if (isLoad && !_alphaTabInstance) {
_alphaTabInstance = true;
const script = document.createElement("script");
script.id = scriptId;
script.src = "https://accounts.google.com/gsi/client";
return new Promise((resolve, reject) => {
script.onload = () => {
_alphaTabInstance = true;
resolve(void 0);
};
script.onerror = (error) => {
Logs.error("Error: ", error);
_alphaTabInstance = false;
reject(error);
};
document.head.appendChild(script);
});
} else {
const ele = document.getElementById(scriptId);
const eleService = document.getElementById("googleidentityservice");
const eleStyles = document.getElementById(
"googleidentityservice_button_styles"
);
ele && ele.remove();
eleService && eleService.remove();
eleStyles && eleStyles.remove();
_alphaTabInstance = false;
}
}
/**
* 库初始化及样式
* @link https://developers.google.cn/identity/gsi/web/reference/js-reference?hl=zh-cn#IdConfiguration
*/
const GoogleSignIn = function (props: {
clientId: string;
callback?: (props: GoogleCallbackProps) => void;
cancel?: (props: GoogleCallbackProps) => void;
}) {
const buttonId = "google-login-button";
const buttonConfig = {
theme: "outline",
size: "large",
text: "login_with",
shape: "rectangular",
width: 351,
};
const IdConfiguration = {
client_id: props.clientId,
use_fedcm_for_prompt: true,
cancel_on_tap_outside: true, // 控制是否在提示之外进行点击时取消提示(关闭一键登录弹窗),默认true
auto_select: false, // 开启自动登录功能,默认false
login_uri: "https://localhost:8080/api/bcc/auth/login",
context: "use",
// state_cookie_domain: "localhost",
ux_mode: "redirect",
itp_support: true,
callback: props?.callback || undefined, // 验证成功回调
cancel: props?.cancel || undefined,
};
useEffect(() => {
// @ts-ignore
if (!window.google) {
loadGoogleGsi(true)?.then(() => {
// @ts-ignore
const googleApi = window.google;
try {
googleApi.accounts.id.initialize(IdConfiguration);
// 渲染“使用 Google 帐号登录”按钮
googleApi.accounts.id.renderButton(
document.getElementById(buttonId),
buttonConfig
);
// 启用一键登录提示(弹窗)功能
googleApi.accounts.id.prompt();
// setTimeout(()=>{
// googleApi.accounts.id.disableAutoSelect();
// }, 5000)
} catch (e) {
Logs.debug("googleApi->Initialized Error:", e);
}
});
}
return () => {
loadGoogleGsi(false);
// @ts-ignore
window.google = undefined;
};
}, []);
return (
<>
<div id={buttonId}></div>
</>
);
};
// 上层调用
const Channel = function () {
const { i18n } = useTranslation();
function handleGoogleSignIn(props: GoogleCallbackProps) {
GoogleVerify(AuthServerConfigs.Google.clientId, props.credential)
.then((res) => {
console.log("handleGoogleSignIn->then:", res);
})
.catch((err) => {
console.log("handleGoogleSignIn->catch:", err);
});
}
function handleGoogleSignInCancel(props: GoogleCallbackProps) {
console.log("handleGoogleSignInCancel:", props);
}
return (
<>
{i18n.language === "en" && (
<GoogleSignIn
clientId={AuthServerConfigs.Google.clientId}
callback={handleGoogleSignIn}
cancel={handleGoogleSignInCancel}
/>
)}
</>
);
};
export const GoogleChannel = Channel;
export default Channel;
回调验证部分
安装依赖库:
google-auth-library
npm install google-auth-library
# or
pnpm add google-auth-library
"use server";
import { OAuth2Client } from "google-auth-library";
import { AuthServerConfigs } from "@/configs/server.configs";
const client = new OAuth2Client({
clientId: AuthServerConfigs.Google.clientId,
clientSecret: AuthServerConfigs.Google.clientSecret,
});
/**
*
* @link https://developers.google.cn/identity/gsi/web/guides/verify-google-id-token?hl=zh-cn
* @param clientId
* @param token
*/
export async function GoogleVerify(clientId: string | string[], token: string) {
return new Promise((resolve, reject) => {
client
.verifyIdToken({
idToken: token,
audience: clientId, // 指定访问后端的应用程序的CLIENT_ID
// 或者,如果多个客户端访问后端:[CLIENT_ID_1, CLIENT_ID_2, CLIENT_ID_3]
})
.then((ticket) => {
console.log("ticket:", ticket);
const payload: any = ticket.getPayload();
console.log("payload:", payload);
const userid = payload["sub"];
resolve({ userid, payload });
})
.catch((error) => {
reject(error);
});
});
}