2025-11-13 08:38:25 +00:00
|
|
|
|
# Google Identity Services (GIS) 登录集成文档
|
|
|
|
|
|
|
|
|
|
|
|
## 概述
|
|
|
|
|
|
|
|
|
|
|
|
使用最新的 **Google Identity Services (GIS)** SDK 实现 Google 登录,无需页面跳转,通过弹窗方式完成授权,用户体验更好。
|
|
|
|
|
|
|
|
|
|
|
|
## 新旧方式对比
|
|
|
|
|
|
|
|
|
|
|
|
### 旧方式(OAuth 2.0 重定向流程)
|
2025-11-28 06:31:36 +00:00
|
|
|
|
|
2025-11-13 08:38:25 +00:00
|
|
|
|
```
|
|
|
|
|
|
用户点击按钮 → 跳转到 Google 授权页面 → 授权后重定向回应用
|
|
|
|
|
|
```
|
2025-11-28 06:31:36 +00:00
|
|
|
|
|
2025-11-13 08:38:25 +00:00
|
|
|
|
❌ 需要页面跳转
|
|
|
|
|
|
❌ 需要配置回调路由
|
2025-11-28 06:31:36 +00:00
|
|
|
|
❌ 用户体验不连贯
|
2025-11-13 08:38:25 +00:00
|
|
|
|
|
|
|
|
|
|
### 新方式(Google Identity Services)
|
2025-11-28 06:31:36 +00:00
|
|
|
|
|
2025-11-13 08:38:25 +00:00
|
|
|
|
```
|
|
|
|
|
|
用户点击按钮 → 弹出 Google 授权窗口 → 授权后直接回调
|
|
|
|
|
|
```
|
2025-11-28 06:31:36 +00:00
|
|
|
|
|
2025-11-13 08:38:25 +00:00
|
|
|
|
✅ 无需页面跳转
|
|
|
|
|
|
✅ 无需回调路由
|
|
|
|
|
|
✅ 用户体验流畅
|
2025-11-28 06:31:36 +00:00
|
|
|
|
✅ 更安全(弹窗隔离)
|
2025-11-13 08:38:25 +00:00
|
|
|
|
|
|
|
|
|
|
## 实现架构
|
|
|
|
|
|
|
|
|
|
|
|
### 工作流程
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
用户点击 "Continue with Google"
|
|
|
|
|
|
↓
|
|
|
|
|
|
加载 Google Identity Services SDK
|
|
|
|
|
|
↓
|
|
|
|
|
|
初始化 Code Client
|
|
|
|
|
|
↓
|
|
|
|
|
|
弹出 Google 授权窗口
|
|
|
|
|
|
↓
|
|
|
|
|
|
用户授权
|
|
|
|
|
|
↓
|
|
|
|
|
|
回调函数接收授权码
|
|
|
|
|
|
↓
|
|
|
|
|
|
调用后端登录接口
|
|
|
|
|
|
↓
|
|
|
|
|
|
登录成功,跳转到首页
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 核心文件
|
|
|
|
|
|
|
|
|
|
|
|
### 1. Google OAuth 配置 (`src/lib/oauth/google.ts`)
|
|
|
|
|
|
|
|
|
|
|
|
**主要功能**:
|
2025-11-28 06:31:36 +00:00
|
|
|
|
|
2025-11-13 08:38:25 +00:00
|
|
|
|
- 定义 Google Identity Services 的 TypeScript 类型
|
|
|
|
|
|
- 提供 SDK 加载方法
|
|
|
|
|
|
- 提供 Code Client 初始化方法
|
|
|
|
|
|
|
|
|
|
|
|
**关键代码**:
|
2025-11-28 06:31:36 +00:00
|
|
|
|
|
2025-11-13 08:38:25 +00:00
|
|
|
|
```typescript
|
|
|
|
|
|
export const googleOAuth = {
|
|
|
|
|
|
// 加载 Google Identity Services SDK
|
|
|
|
|
|
loadScript: (): Promise<void> => {
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
|
if (window.google?.accounts) {
|
|
|
|
|
|
resolve()
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const script = document.createElement('script')
|
|
|
|
|
|
script.src = 'https://accounts.google.com/gsi/client'
|
|
|
|
|
|
script.async = true
|
|
|
|
|
|
script.defer = true
|
|
|
|
|
|
script.onload = () => resolve()
|
|
|
|
|
|
script.onerror = () => reject(new Error('Failed to load SDK'))
|
2025-11-28 06:31:36 +00:00
|
|
|
|
|
2025-11-13 08:38:25 +00:00
|
|
|
|
document.head.appendChild(script)
|
|
|
|
|
|
})
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化 Code Client(获取授权码)
|
|
|
|
|
|
initCodeClient: (callback, errorCallback) => {
|
|
|
|
|
|
return window.google.accounts.oauth2.initCodeClient({
|
|
|
|
|
|
client_id: GOOGLE_CLIENT_ID,
|
|
|
|
|
|
scope: GOOGLE_SCOPES,
|
|
|
|
|
|
ux_mode: 'popup',
|
|
|
|
|
|
callback,
|
2025-11-28 06:31:36 +00:00
|
|
|
|
error_callback: errorCallback,
|
2025-11-13 08:38:25 +00:00
|
|
|
|
})
|
2025-11-28 06:31:36 +00:00
|
|
|
|
},
|
2025-11-13 08:38:25 +00:00
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 2. GoogleButton 组件 (`src/app/(auth)/login/components/GoogleButton.tsx`)
|
|
|
|
|
|
|
|
|
|
|
|
**主要功能**:
|
2025-11-28 06:31:36 +00:00
|
|
|
|
|
2025-11-13 08:38:25 +00:00
|
|
|
|
- 加载 Google Identity Services SDK
|
|
|
|
|
|
- 初始化 Code Client
|
|
|
|
|
|
- 处理授权码回调
|
|
|
|
|
|
- 调用后端登录接口
|
|
|
|
|
|
|
|
|
|
|
|
**关键实现**:
|
|
|
|
|
|
|
|
|
|
|
|
#### SDK 加载
|
2025-11-28 06:31:36 +00:00
|
|
|
|
|
2025-11-13 08:38:25 +00:00
|
|
|
|
```typescript
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
const loadGoogleSDK = async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
await googleOAuth.loadScript()
|
|
|
|
|
|
console.log('Google Identity Services SDK loaded')
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Failed to load Google SDK:', error)
|
2025-11-28 06:31:36 +00:00
|
|
|
|
toast.error('Failed to load Google login')
|
2025-11-13 08:38:25 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
loadGoogleSDK()
|
|
|
|
|
|
}, [])
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 授权码处理
|
2025-11-28 06:31:36 +00:00
|
|
|
|
|
2025-11-13 08:38:25 +00:00
|
|
|
|
```typescript
|
|
|
|
|
|
const handleGoogleResponse = async (response: GoogleCodeResponse) => {
|
|
|
|
|
|
const deviceId = tokenManager.getDeviceId()
|
|
|
|
|
|
const loginData = {
|
|
|
|
|
|
appClient: AppClient.Web,
|
|
|
|
|
|
deviceCode: deviceId,
|
|
|
|
|
|
thirdToken: response.code, // Google 授权码
|
2025-11-28 06:31:36 +00:00
|
|
|
|
thirdType: ThirdType.Google,
|
2025-11-13 08:38:25 +00:00
|
|
|
|
}
|
2025-11-28 06:31:36 +00:00
|
|
|
|
|
2025-11-13 08:38:25 +00:00
|
|
|
|
login.mutate(loginData, {
|
|
|
|
|
|
onSuccess: () => {
|
2025-11-28 06:31:36 +00:00
|
|
|
|
toast.success('Login successful')
|
2025-11-13 08:38:25 +00:00
|
|
|
|
router.push('/')
|
|
|
|
|
|
},
|
|
|
|
|
|
onError: (error) => {
|
2025-11-28 06:31:36 +00:00
|
|
|
|
toast.error('Login failed')
|
|
|
|
|
|
},
|
2025-11-13 08:38:25 +00:00
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 登录按钮点击
|
2025-11-28 06:31:36 +00:00
|
|
|
|
|
2025-11-13 08:38:25 +00:00
|
|
|
|
```typescript
|
|
|
|
|
|
const handleGoogleLogin = async () => {
|
|
|
|
|
|
// 确保 SDK 已加载
|
|
|
|
|
|
if (!window.google?.accounts?.oauth2) {
|
|
|
|
|
|
await googleOAuth.loadScript()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化 Code Client
|
|
|
|
|
|
if (!codeClientRef.current) {
|
2025-11-28 06:31:36 +00:00
|
|
|
|
codeClientRef.current = googleOAuth.initCodeClient(handleGoogleResponse, handleGoogleError)
|
2025-11-13 08:38:25 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 请求授权码(弹出授权窗口)
|
|
|
|
|
|
codeClientRef.current.requestCode()
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 环境变量配置
|
|
|
|
|
|
|
|
|
|
|
|
只需要配置客户端 ID,不需要配置回调 URL:
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
# .env.local
|
|
|
|
|
|
NEXT_PUBLIC_GOOGLE_CLIENT_ID=你的Google客户端ID
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 获取 Google OAuth 凭据
|
|
|
|
|
|
|
|
|
|
|
|
1. 访问 [Google Cloud Console](https://console.cloud.google.com/)
|
|
|
|
|
|
2. 创建或选择项目
|
|
|
|
|
|
3. 启用 Google+ API
|
|
|
|
|
|
4. 创建 OAuth 2.0 客户端 ID
|
|
|
|
|
|
5. 应用类型选择 "Web 应用"
|
|
|
|
|
|
6. **授权的 JavaScript 来源**添加:
|
|
|
|
|
|
```
|
|
|
|
|
|
http://localhost:3000
|
|
|
|
|
|
https://test.crushlevel.ai
|
|
|
|
|
|
```
|
|
|
|
|
|
7. **授权的重定向 URI** 可以留空(GIS 不需要)
|
|
|
|
|
|
8. 复制客户端 ID
|
|
|
|
|
|
|
|
|
|
|
|
## 后端接口要求
|
|
|
|
|
|
|
|
|
|
|
|
与之前相同,后端接收授权码并完成登录:
|
|
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
|
POST /api/auth/login
|
|
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
"appClient": "WEB",
|
|
|
|
|
|
"deviceCode": "设备ID",
|
|
|
|
|
|
"thirdToken": "Google授权码",
|
|
|
|
|
|
"thirdType": "GOOGLE"
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
后端需要:
|
2025-11-28 06:31:36 +00:00
|
|
|
|
|
2025-11-13 08:38:25 +00:00
|
|
|
|
1. 使用授权码向 Google 交换 access_token
|
|
|
|
|
|
2. 使用 access_token 获取用户信息
|
|
|
|
|
|
3. 创建或更新用户
|
|
|
|
|
|
4. 返回应用的登录 token
|
|
|
|
|
|
|
|
|
|
|
|
## 优势
|
|
|
|
|
|
|
|
|
|
|
|
### 1. 更好的用户体验
|
2025-11-28 06:31:36 +00:00
|
|
|
|
|
2025-11-13 08:38:25 +00:00
|
|
|
|
- ✅ 无需离开当前页面
|
|
|
|
|
|
- ✅ 弹窗授权,快速完成
|
|
|
|
|
|
- ✅ 不打断用户操作流程
|
|
|
|
|
|
|
|
|
|
|
|
### 2. 更简单的实现
|
2025-11-28 06:31:36 +00:00
|
|
|
|
|
2025-11-13 08:38:25 +00:00
|
|
|
|
- ✅ 不需要回调路由
|
|
|
|
|
|
- ✅ 不需要处理 URL 参数
|
|
|
|
|
|
- ✅ 不需要 state 验证
|
|
|
|
|
|
- ✅ 代码更简洁
|
|
|
|
|
|
|
|
|
|
|
|
### 3. 更安全
|
2025-11-28 06:31:36 +00:00
|
|
|
|
|
2025-11-13 08:38:25 +00:00
|
|
|
|
- ✅ 弹窗隔离,防止钓鱼
|
|
|
|
|
|
- ✅ SDK 自动处理安全验证
|
|
|
|
|
|
- ✅ 支持 CORS 和 CSP
|
|
|
|
|
|
|
|
|
|
|
|
### 4. 更现代
|
2025-11-28 06:31:36 +00:00
|
|
|
|
|
2025-11-13 08:38:25 +00:00
|
|
|
|
- ✅ Google 官方推荐方式
|
|
|
|
|
|
- ✅ 持续维护和更新
|
|
|
|
|
|
- ✅ 更好的浏览器兼容性
|
|
|
|
|
|
|
|
|
|
|
|
## 与旧实现的对比
|
|
|
|
|
|
|
2025-11-28 06:31:36 +00:00
|
|
|
|
| 特性 | 旧方式(重定向) | 新方式(GIS) |
|
|
|
|
|
|
| ------------ | ---------------- | --------------- |
|
|
|
|
|
|
| 页面跳转 | ✅ 需要 | ❌ 不需要 |
|
|
|
|
|
|
| 回调路由 | ✅ 需要 | ❌ 不需要 |
|
|
|
|
|
|
| State 验证 | ✅ 需要手动实现 | ❌ SDK 自动处理 |
|
|
|
|
|
|
| URL 参数处理 | ✅ 需要 | ❌ 不需要 |
|
|
|
|
|
|
| 用户体验 | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
|
|
|
|
|
|
| 代码复杂度 | 高 | 低 |
|
|
|
|
|
|
| 维护成本 | 高 | 低 |
|
2025-11-13 08:38:25 +00:00
|
|
|
|
|
|
|
|
|
|
## 常见问题
|
|
|
|
|
|
|
|
|
|
|
|
### Q: SDK 加载失败怎么办?
|
2025-11-28 06:31:36 +00:00
|
|
|
|
|
|
|
|
|
|
A:
|
|
|
|
|
|
|
2025-11-13 08:38:25 +00:00
|
|
|
|
- 检查网络连接
|
|
|
|
|
|
- 确认没有被广告拦截器阻止
|
|
|
|
|
|
- 检查浏览器控制台错误信息
|
|
|
|
|
|
|
|
|
|
|
|
### Q: 弹窗被浏览器拦截?
|
2025-11-28 06:31:36 +00:00
|
|
|
|
|
|
|
|
|
|
A:
|
|
|
|
|
|
|
2025-11-13 08:38:25 +00:00
|
|
|
|
- 确保在用户点击事件中调用 `requestCode()`
|
|
|
|
|
|
- 不要在异步操作后调用
|
|
|
|
|
|
- 检查浏览器弹窗设置
|
|
|
|
|
|
|
|
|
|
|
|
### Q: 授权后没有回调?
|
2025-11-28 06:31:36 +00:00
|
|
|
|
|
|
|
|
|
|
A:
|
|
|
|
|
|
|
2025-11-13 08:38:25 +00:00
|
|
|
|
- 检查回调函数是否正确绑定
|
|
|
|
|
|
- 查看浏览器控制台是否有错误
|
|
|
|
|
|
- 确认 Client ID 配置正确
|
|
|
|
|
|
|
|
|
|
|
|
### Q: 用户取消授权如何处理?
|
2025-11-28 06:31:36 +00:00
|
|
|
|
|
|
|
|
|
|
A:
|
|
|
|
|
|
|
2025-11-13 08:38:25 +00:00
|
|
|
|
```typescript
|
|
|
|
|
|
const handleGoogleError = (error: any) => {
|
|
|
|
|
|
// 用户取消授权不显示错误提示
|
|
|
|
|
|
if (error.type === 'popup_closed') {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
2025-11-28 06:31:36 +00:00
|
|
|
|
|
|
|
|
|
|
toast.error('Google login failed')
|
2025-11-13 08:38:25 +00:00
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 测试清单
|
|
|
|
|
|
|
|
|
|
|
|
### 本地测试
|
2025-11-28 06:31:36 +00:00
|
|
|
|
|
2025-11-13 08:38:25 +00:00
|
|
|
|
- [ ] SDK 正常加载
|
|
|
|
|
|
- [ ] 点击按钮弹出授权窗口
|
|
|
|
|
|
- [ ] 授权后正确回调
|
|
|
|
|
|
- [ ] 授权码正确传递给后端
|
|
|
|
|
|
- [ ] 登录成功后正确跳转
|
|
|
|
|
|
- [ ] 用户取消授权的处理
|
|
|
|
|
|
- [ ] 错误情况的处理
|
|
|
|
|
|
|
|
|
|
|
|
### 生产环境测试
|
2025-11-28 06:31:36 +00:00
|
|
|
|
|
2025-11-13 08:38:25 +00:00
|
|
|
|
- [ ] 配置正确的 JavaScript 来源
|
|
|
|
|
|
- [ ] HTTPS 证书有效
|
|
|
|
|
|
- [ ] 环境变量配置正确
|
|
|
|
|
|
- [ ] 后端接口正常工作
|
|
|
|
|
|
- [ ] 不同浏览器测试
|
|
|
|
|
|
|
|
|
|
|
|
## 浏览器兼容性
|
|
|
|
|
|
|
|
|
|
|
|
Google Identity Services 支持:
|
2025-11-28 06:31:36 +00:00
|
|
|
|
|
2025-11-13 08:38:25 +00:00
|
|
|
|
- ✅ Chrome 90+
|
|
|
|
|
|
- ✅ Firefox 88+
|
|
|
|
|
|
- ✅ Safari 14+
|
|
|
|
|
|
- ✅ Edge 90+
|
|
|
|
|
|
|
|
|
|
|
|
## 安全注意事项
|
|
|
|
|
|
|
|
|
|
|
|
### 1. 客户端 ID 保护
|
2025-11-28 06:31:36 +00:00
|
|
|
|
|
2025-11-13 08:38:25 +00:00
|
|
|
|
虽然客户端 ID 是公开的,但仍需注意:
|
2025-11-28 06:31:36 +00:00
|
|
|
|
|
2025-11-13 08:38:25 +00:00
|
|
|
|
- 限制授权的 JavaScript 来源
|
|
|
|
|
|
- 定期检查使用情况
|
|
|
|
|
|
- 发现异常及时更换
|
|
|
|
|
|
|
|
|
|
|
|
### 2. 授权码处理
|
2025-11-28 06:31:36 +00:00
|
|
|
|
|
2025-11-13 08:38:25 +00:00
|
|
|
|
- 授权码只能使用一次
|
|
|
|
|
|
- 及时传递给后端
|
|
|
|
|
|
- 不要在客户端存储
|
|
|
|
|
|
|
|
|
|
|
|
### 3. HTTPS 要求
|
2025-11-28 06:31:36 +00:00
|
|
|
|
|
2025-11-13 08:38:25 +00:00
|
|
|
|
- 生产环境必须使用 HTTPS
|
|
|
|
|
|
- 本地开发可以使用 HTTP
|
|
|
|
|
|
|
|
|
|
|
|
## 迁移指南
|
|
|
|
|
|
|
|
|
|
|
|
如果你之前使用的是旧的重定向方式,迁移步骤:
|
|
|
|
|
|
|
|
|
|
|
|
1. **更新配置文件**
|
|
|
|
|
|
- 使用新的 `src/lib/oauth/google.ts`
|
|
|
|
|
|
|
|
|
|
|
|
2. **更新组件**
|
|
|
|
|
|
- 使用新的 `GoogleButton.tsx`
|
|
|
|
|
|
|
|
|
|
|
|
3. **删除回调路由**
|
|
|
|
|
|
- 删除 `src/app/api/auth/google/callback/route.ts`
|
|
|
|
|
|
|
|
|
|
|
|
4. **更新 Google Cloud Console**
|
|
|
|
|
|
- 添加授权的 JavaScript 来源
|
|
|
|
|
|
- 可以移除重定向 URI(可选)
|
|
|
|
|
|
|
|
|
|
|
|
5. **测试**
|
|
|
|
|
|
- 完整测试登录流程
|
|
|
|
|
|
- 确认所有功能正常
|
|
|
|
|
|
|
|
|
|
|
|
## 扩展功能
|
|
|
|
|
|
|
|
|
|
|
|
### 1. One Tap 登录
|
2025-11-28 06:31:36 +00:00
|
|
|
|
|
2025-11-13 08:38:25 +00:00
|
|
|
|
可以添加 Google One Tap 功能,自动显示登录提示:
|
|
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
|
window.google.accounts.id.initialize({
|
|
|
|
|
|
client_id: GOOGLE_CLIENT_ID,
|
2025-11-28 06:31:36 +00:00
|
|
|
|
callback: handleCredentialResponse,
|
2025-11-13 08:38:25 +00:00
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
window.google.accounts.id.prompt()
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 2. 自动登录
|
2025-11-28 06:31:36 +00:00
|
|
|
|
|
2025-11-13 08:38:25 +00:00
|
|
|
|
可以实现自动登录功能:
|
|
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
|
window.google.accounts.id.initialize({
|
|
|
|
|
|
client_id: GOOGLE_CLIENT_ID,
|
|
|
|
|
|
callback: handleCredentialResponse,
|
2025-11-28 06:31:36 +00:00
|
|
|
|
auto_select: true,
|
2025-11-13 08:38:25 +00:00
|
|
|
|
})
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 3. 自定义按钮样式
|
2025-11-28 06:31:36 +00:00
|
|
|
|
|
2025-11-13 08:38:25 +00:00
|
|
|
|
可以使用 Google 提供的标准按钮:
|
|
|
|
|
|
|
|
|
|
|
|
```typescript
|
2025-11-28 06:31:36 +00:00
|
|
|
|
window.google.accounts.id.renderButton(document.getElementById('buttonDiv'), {
|
|
|
|
|
|
theme: 'outline',
|
|
|
|
|
|
size: 'large',
|
|
|
|
|
|
})
|
2025-11-13 08:38:25 +00:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 相关文档
|
|
|
|
|
|
|
|
|
|
|
|
- [Google Identity Services 官方文档](https://developers.google.com/identity/gsi/web)
|
|
|
|
|
|
- [Code Model 文档](https://developers.google.com/identity/oauth2/web/guides/use-code-model)
|
|
|
|
|
|
- [迁移指南](https://developers.google.com/identity/gsi/web/guides/migration)
|
|
|
|
|
|
|
|
|
|
|
|
## 总结
|
|
|
|
|
|
|
|
|
|
|
|
使用 Google Identity Services 是 Google 官方推荐的最新方式,相比传统的 OAuth 重定向流程:
|
|
|
|
|
|
|
|
|
|
|
|
✅ **用户体验更好** - 无需页面跳转
|
|
|
|
|
|
✅ **实现更简单** - 代码量更少
|
|
|
|
|
|
✅ **维护更容易** - 无需处理复杂的回调
|
2025-11-28 06:31:36 +00:00
|
|
|
|
✅ **更加安全** - SDK 自动处理安全验证
|
2025-11-13 08:38:25 +00:00
|
|
|
|
|
|
|
|
|
|
强烈建议新项目直接使用这种方式!
|