314 lines
7.9 KiB
Markdown
314 lines
7.9 KiB
Markdown
# Google OAuth 登录集成文档
|
||
|
||
## 功能概述
|
||
|
||
实现了 Google OAuth 2.0 登录功能,参考 Discord 登录的实现模式,用户可以通过 Google 账号快速登录应用。
|
||
|
||
## 实现架构
|
||
|
||
### 1. OAuth 流程
|
||
|
||
```
|
||
用户点击 "Continue with Google"
|
||
↓
|
||
跳转到 Google 授权页面
|
||
↓
|
||
用户授权后,Google 重定向到回调 URL
|
||
↓
|
||
API 路由接收授权码并重定向回登录页
|
||
↓
|
||
前端获取授权码并调用后端登录接口
|
||
↓
|
||
登录成功,跳转到首页或指定页面
|
||
```
|
||
|
||
## 文件结构
|
||
|
||
```
|
||
src/
|
||
├── lib/
|
||
│ └── oauth/
|
||
│ ├── discord.ts # Discord OAuth 配置
|
||
│ └── google.ts # Google OAuth 配置 (新增)
|
||
├── app/
|
||
│ ├── (auth)/
|
||
│ │ └── login/
|
||
│ │ └── components/
|
||
│ │ ├── DiscordButton.tsx # Discord 登录按钮
|
||
│ │ ├── GoogleButton.tsx # Google 登录按钮 (新增)
|
||
│ │ └── login-form.tsx # 登录表单 (已更新)
|
||
│ └── api/
|
||
│ └── auth/
|
||
│ ├── discord/
|
||
│ │ └── callback/
|
||
│ │ └── route.ts # Discord 回调路由
|
||
│ └── google/
|
||
│ └── callback/
|
||
│ └── route.ts # Google 回调路由 (新增)
|
||
```
|
||
|
||
## 核心文件说明
|
||
|
||
### 1. Google OAuth 配置 (`src/lib/oauth/google.ts`)
|
||
|
||
```typescript
|
||
export const googleOAuth = {
|
||
getAuthUrl: (state?: string): string => {
|
||
// 构建 Google OAuth 授权 URL
|
||
// 包含 client_id, redirect_uri, scope 等参数
|
||
},
|
||
}
|
||
```
|
||
|
||
**配置参数**:
|
||
|
||
- `client_id`: Google OAuth 客户端 ID
|
||
- `redirect_uri`: 授权后的回调 URL
|
||
- `scope`: 请求的权限范围(email, profile)
|
||
- `access_type`: offline(获取 refresh_token)
|
||
- `prompt`: consent(每次都显示授权页面)
|
||
|
||
### 2. GoogleButton 组件 (`src/app/(auth)/login/components/GoogleButton.tsx`)
|
||
|
||
**功能**:
|
||
|
||
- 处理 Google 登录按钮点击事件
|
||
- 生成随机 state 用于安全验证
|
||
- 跳转到 Google 授权页面
|
||
- 处理 OAuth 回调(授权码)
|
||
- 调用后端登录接口
|
||
- 处理登录成功/失败的重定向
|
||
|
||
**关键方法**:
|
||
|
||
```typescript
|
||
const handleGoogleLogin = () => {
|
||
// 1. 生成 state
|
||
const state = Math.random().toString(36).substring(2, 15)
|
||
|
||
// 2. 获取授权 URL
|
||
const authUrl = googleOAuth.getAuthUrl(state)
|
||
|
||
// 3. 保存 state 到 sessionStorage
|
||
sessionStorage.setItem('google_oauth_state', state)
|
||
|
||
// 4. 跳转到 Google 授权页面
|
||
window.location.href = authUrl
|
||
}
|
||
```
|
||
|
||
**OAuth 回调处理**:
|
||
|
||
```typescript
|
||
useEffect(() => {
|
||
const googleCode = searchParams.get('google_code')
|
||
const googleState = searchParams.get('google_state')
|
||
|
||
if (googleCode) {
|
||
// 验证 state
|
||
// 调用后端登录接口
|
||
// 处理登录结果
|
||
}
|
||
}, [])
|
||
```
|
||
|
||
### 3. Google 回调路由 (`src/app/api/auth/google/callback/route.ts`)
|
||
|
||
**功能**:
|
||
|
||
- 接收 Google OAuth 回调
|
||
- 提取授权码 (code) 和 state
|
||
- 重定向回登录页面,并将参数传递给前端
|
||
|
||
```typescript
|
||
export async function GET(request: NextRequest) {
|
||
const code = searchParams.get('code')
|
||
const state = searchParams.get('state')
|
||
|
||
// 重定向到登录页,携带 google_code 和 google_state
|
||
redirectUrl.searchParams.set('google_code', code)
|
||
redirectUrl.searchParams.set('google_state', state)
|
||
|
||
return NextResponse.redirect(redirectUrl)
|
||
}
|
||
```
|
||
|
||
## 环境变量配置
|
||
|
||
需要在 `.env.local` 中添加以下环境变量:
|
||
|
||
```bash
|
||
# Google OAuth 配置
|
||
NEXT_PUBLIC_GOOGLE_CLIENT_ID=your_google_client_id_here
|
||
NEXT_PUBLIC_APP_URL=https://test.crushlevel.ai
|
||
```
|
||
|
||
### 获取 Google OAuth 凭据
|
||
|
||
1. 访问 [Google Cloud Console](https://console.cloud.google.com/)
|
||
2. 创建或选择一个项目
|
||
3. 启用 Google+ API
|
||
4. 创建 OAuth 2.0 客户端 ID
|
||
5. 配置授权重定向 URI:
|
||
```
|
||
https://test.crushlevel.ai/api/auth/google/callback
|
||
http://localhost:3000/api/auth/google/callback (开发环境)
|
||
```
|
||
6. 复制客户端 ID 到环境变量
|
||
|
||
## 后端接口要求
|
||
|
||
后端需要实现登录接口,接收以下参数:
|
||
|
||
```typescript
|
||
interface LoginRequest {
|
||
appClient: AppClient.Web
|
||
deviceCode: string // 设备唯一标识
|
||
thirdToken: string // Google 授权码
|
||
thirdType: ThirdType.Google // 第三方类型
|
||
}
|
||
```
|
||
|
||
后端需要:
|
||
|
||
1. 使用授权码向 Google 交换 access_token
|
||
2. 使用 access_token 获取用户信息
|
||
3. 创建或更新用户账号
|
||
4. 返回应用的登录 token
|
||
|
||
## 安全特性
|
||
|
||
### 1. State 参数验证
|
||
|
||
- 前端生成随机 state 并保存到 sessionStorage
|
||
- 回调时验证 state 是否匹配
|
||
- 防止 CSRF 攻击
|
||
|
||
### 2. 授权码模式
|
||
|
||
- 使用 OAuth 2.0 授权码流程
|
||
- 授权码只能使用一次
|
||
- Token 交换在后端进行,更安全
|
||
|
||
### 3. URL 参数清理
|
||
|
||
- 登录成功后清理 URL 中的敏感参数
|
||
- 防止参数泄露
|
||
|
||
## 用户体验优化
|
||
|
||
### 1. 重定向保持
|
||
|
||
```typescript
|
||
// 保存登录前的页面
|
||
sessionStorage.setItem('login_redirect_url', redirect || '')
|
||
|
||
// 登录成功后跳转回原页面
|
||
const loginRedirectUrl = sessionStorage.getItem('login_redirect_url')
|
||
if (loginRedirectUrl) {
|
||
router.push(loginRedirectUrl)
|
||
}
|
||
```
|
||
|
||
### 2. 错误处理
|
||
|
||
- 授权失败时显示友好的错误提示
|
||
- 自动清理 URL 参数
|
||
- 不影响用户继续尝试登录
|
||
|
||
### 3. 加载状态
|
||
|
||
- 使用 `useLogin` Hook 的 loading 状态
|
||
- 可以添加 loading 动画提升体验
|
||
|
||
## 测试清单
|
||
|
||
### 本地测试
|
||
|
||
- [ ] 点击 Google 登录按钮跳转到 Google 授权页面
|
||
- [ ] 授权后正确回调到应用
|
||
- [ ] 授权码正确传递给后端
|
||
- [ ] 登录成功后跳转到首页
|
||
- [ ] State 参数验证正常工作
|
||
- [ ] 错误情况处理正确
|
||
|
||
### 生产环境测试
|
||
|
||
- [ ] 配置正确的回调 URL
|
||
- [ ] HTTPS 证书有效
|
||
- [ ] 环境变量配置正确
|
||
- [ ] 后端接口正常工作
|
||
|
||
## 常见问题
|
||
|
||
### 1. 回调 URL 不匹配
|
||
|
||
**错误**: `redirect_uri_mismatch`
|
||
|
||
**解决方案**:
|
||
|
||
- 检查 Google Cloud Console 中配置的回调 URL
|
||
- 确保 `NEXT_PUBLIC_APP_URL` 环境变量正确
|
||
- 开发环境和生产环境需要分别配置
|
||
|
||
### 2. State 验证失败
|
||
|
||
**错误**: "Google login failed"
|
||
|
||
**解决方案**:
|
||
|
||
- 检查 sessionStorage 是否正常工作
|
||
- 确保没有跨域问题
|
||
- 检查浏览器是否禁用了 cookie/storage
|
||
|
||
### 3. 授权码已使用
|
||
|
||
**错误**: 后端返回授权码无效
|
||
|
||
**解决方案**:
|
||
|
||
- 授权码只能使用一次
|
||
- 避免重复调用登录接口
|
||
- 清理 URL 参数防止页面刷新时重复使用
|
||
|
||
## 与 Discord 登录的对比
|
||
|
||
| 特性 | Discord | Google |
|
||
| -------------- | -------------------------------- | ------------------------------------ |
|
||
| OAuth Provider | Discord | Google |
|
||
| Scopes | identify, email | userinfo.email, userinfo.profile |
|
||
| 授权 URL | discord.com/api/oauth2/authorize | accounts.google.com/o/oauth2/v2/auth |
|
||
| 回调路由 | /api/auth/discord/callback | /api/auth/google/callback |
|
||
| URL 参数 | discord_code, discord_state | google_code, google_state |
|
||
| ThirdType | Discord | Google |
|
||
|
||
## 扩展建议
|
||
|
||
### 1. 添加 Apple 登录
|
||
|
||
参考 Google 登录的实现,创建:
|
||
|
||
- `src/lib/oauth/apple.ts`
|
||
- `src/app/(auth)/login/components/AppleButton.tsx`
|
||
- `src/app/api/auth/apple/callback/route.ts`
|
||
|
||
### 2. 统一 OAuth 处理
|
||
|
||
可以创建通用的 OAuth Hook:
|
||
|
||
```typescript
|
||
const useOAuthLogin = (provider: 'google' | 'discord' | 'apple') => {
|
||
// 通用的 OAuth 登录逻辑
|
||
}
|
||
```
|
||
|
||
### 3. 添加登录统计
|
||
|
||
记录不同登录方式的使用情况,优化用户体验。
|
||
|
||
## 相关文档
|
||
|
||
- [Google OAuth 2.0 文档](https://developers.google.com/identity/protocols/oauth2)
|
||
- [Next.js API Routes](https://nextjs.org/docs/app/building-your-application/routing/route-handlers)
|
||
- Discord OAuth 实现参考
|