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