290 lines
7.6 KiB
Markdown
290 lines
7.6 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 实现参考
|
|||
|
|
|