13 KiB
Google OAuth 接入详细步骤
1. 创建 Google Cloud 项目
1.1 进入 Google Cloud Console
- 打开 https://console.cloud.google.com/
- 使用 Google 账号登录
1.2 创建新项目
- 点击顶部导航栏的项目选择器
- 点击 NEW PROJECT
- 填写:
- Project name:
raisfast - Organization: 保持默认
- Project name:
- 点击 CREATE
- 等待项目创建完成,选择该项目
2. 配置 OAuth 同意屏幕
Google 要求所有 OAuth 应用必须配置同意屏幕,否则无法使用。
2.1 进入配置页面
- 左侧菜单 → APIs & Services → OAuth consent screen
- 直接链接:https://console.cloud.google.com/apis/credentials/consent
2.2 选择用户类型
| 用户类型 | 适用场景 |
|---|---|
| External | 任何 Google 账号都可登录(推荐) |
| Internal | 仅组织内 G Suite 账号可登录 |
选择 External → 点击 CREATE
2.3 填写应用信息
OAuth consent screen 页面:
| 字段 | 值 |
|---|---|
| App name | raisfast |
| User support email | 你的邮箱 |
| App logo | (可选,后续上传) |
App domain(全部可选):
| 字段 | 值 |
|---|---|
| Application home page | http://localhost:3000 |
| Application privacy policy link | (可选) |
| Application terms of service link | (可选) |
Authorized domains:
点击 ADD DOMAIN,添加:
| 域名 |
|---|
localhost |
生产环境添加你的域名如
yourdomain.com
Developer contact information:
| 字段 | 值 |
|---|---|
| Email addresses | 你的邮箱 |
点击 SAVE AND CONTINUE
2.4 配置 Scopes
- 点击 ADD OR REMOVE SCOPES
- 搜索并勾选以下 scope:
| Scope | 说明 |
|---|---|
.../auth/userinfo.email |
读取用户邮箱 |
.../auth/userinfo.profile |
读取用户基本信息(名字、头像) |
openid |
OpenID Connect |
- 点击 UPDATE → SAVE AND CONTINUE
2.5 添加测试用户(发布前必须)
如果应用状态为 Testing,只有添加的测试用户可以登录。
- 点击 ADD USERS
- 输入你的 Gmail 邮箱
- 点击 ADD → SAVE AND CONTINUE
2.6 发布应用(可选)
Testing 状态下只有测试用户能登录。如需所有人可登录:
- 回到 OAuth consent screen
- 点击 PUBLISH APP
- 确认发布
注意:发布后如果请求敏感 scope 可能需要 Google 审核。
profile是非敏感的,无需审核。
3. 创建 OAuth 2.0 凭证
3.1 进入凭证页面
- 左侧菜单 → APIs & Services → Credentials
- 直接链接:https://console.cloud.google.com/apis/credentials
3.2 创建 OAuth 客户端
- 点击顶部 + CREATE CREDENTIALS → OAuth client ID
- 填写:
| 字段 | 值 |
|---|---|
| Application type | Web application |
| Name | raisfast-web |
Authorized JavaScript origins:
点击 ADD URI,添加:
| 环境 | URI |
|---|---|
| 本地开发 | http://localhost:9000 |
| 生产环境 | https://yourdomain.com |
Authorized redirect URIs:
点击 ADD URI,添加:
| 环境 | URI |
|---|---|
| 本地开发 | http://localhost:9000/api/v1/auth/oauth/google/callback |
| 生产环境 | https://yourdomain.com/api/v1/auth/oauth/google/callback |
- 点击 CREATE
3.3 获取凭证
创建完成后弹出窗口显示:
- Your Client ID:形如
123456789-abcxxx.apps.googleusercontent.com - Your Client Secret:形如
GOCSPX-xxxxxxxxxx
点击 DOWNLOAD JSON 备份凭证(可选)。
Client Secret 可以在凭证详情页随时查看,不会像 GitHub 那样只显示一次。
4. 后端代码实现
Google OAuth 需要新增 Provider 实现。创建 src/oauth/google.rs:
4.1 Google Provider 实现
Google 的 OAuth2 端点:
| 端点 | URL |
|---|---|
| Authorization | https://accounts.google.com/o/oauth2/v2/auth |
| Token Exchange | https://oauth2.googleapis.com/token |
| User Info | https://www.googleapis.com/oauth2/v2/userinfo |
4.2 关键差异(对比 GitHub)
| 对比项 | GitHub | |
|---|---|---|
| Scope | user:email |
openid email profile |
| Token 响应 | access_token 在 JSON body |
同 |
| 用户信息 API | GET /user + GET /user/emails |
GET /userinfo(一次请求) |
| 用户 ID | 数字字符串 | 数字字符串(sub 字段) |
| 邮箱 | 可能需要额外请求 | 直接返回,有 verified_email 字段 |
4.3 实现步骤
步骤一:创建 Google Provider 文件
在 src/oauth.rs 中添加 Google Provider(与 GitHub 同文件):
// 在 src/oauth.rs 末尾添加
// ── Google Provider ────────────────────────────────────────────
pub struct GoogleProvider {
client_id: String,
client_secret: String,
}
impl GoogleProvider {
pub fn new(client_id: String, client_secret: String) -> Self {
Self { client_id, client_secret }
}
}
#[async_trait::async_trait]
impl OAuthProvider for GoogleProvider {
fn name(&self) -> &str {
"google"
}
fn authorize_url(&self, state: &str, code_challenge: &str) -> String {
format!(
"https://accounts.google.com/o/oauth2/v2/auth?client_id={}&state={}&code_challenge={}&code_challenge_method=S256&scope=openid+email+profile&response_type=code&access_type=offline",
self.client_id, state, code_challenge
)
}
async fn exchange_code(&self, code: &str, code_verifier: &str)
-> AppResult<OAuthTokenResponse>
{
let client = reqwest::Client::new();
let resp = client
.post("https://oauth2.googleapis.com/token")
.header("Accept", "application/json")
.json(&serde_json::json!({
"client_id": self.client_id,
"client_secret": self.client_secret,
"code": code,
"code_verifier": code_verifier,
"grant_type": "authorization_code",
"redirect_uri": "", // PKCE 模式不需要
}))
.send()
.await
.map_err(|e| AppError::Internal(
anyhow::anyhow!("Google token exchange failed: {e}")
))?;
if !resp.status().is_success() {
let status = resp.status();
let body = resp.text().await.unwrap_or_default();
return Err(AppError::Internal(anyhow::anyhow!(
"Google token exchange returned {status}: {body}"
)));
}
resp.json::<OAuthTokenResponse>().await.map_err(|e| {
AppError::Internal(anyhow::anyhow!(
"Google token response parse failed: {e}"
))
})
}
async fn fetch_user_info(&self, access_token: &str)
-> AppResult<OAuthUserInfo>
{
let client = reqwest::Client::new();
let resp = client
.get("https://www.googleapis.com/oauth2/v2/userinfo")
.header("Authorization", format!("Bearer {access_token}"))
.header("Accept", "application/json")
.send()
.await
.map_err(|e| AppError::Internal(
anyhow::anyhow!("Google user info request failed: {e}")
))?;
if !resp.status().is_success() {
let status = resp.status();
let body = resp.text().await.unwrap_or_default();
return Err(AppError::Internal(anyhow::anyhow!(
"Google user info returned {status}: {body}"
)));
}
let profile: serde_json::Value = resp.json().await.map_err(|e| {
AppError::Internal(anyhow::anyhow!(
"Google user info parse failed: {e}"
))
})?;
let provider_user_id = profile["sub"]
.as_str()
.unwrap_or_default()
.to_string();
let email = profile["email"].as_str().map(|s| s.to_string());
let display_name = profile["name"].as_str()
.or_else(|| profile["given_name"].as_str())
.map(|s| s.to_string());
let avatar_url = profile["picture"].as_str().map(|s| s.to_string());
Ok(OAuthUserInfo {
provider_user_id,
email,
display_name,
avatar_url,
raw_profile: profile,
})
}
}
步骤二:在 server.rs 注册 Google Provider
在 build_oauth_registry 函数中添加:
if let Some(google) = &config.oauth.google {
registry.register(Box::new(
crate::oauth::GoogleProvider::new(
google.client_id.clone(),
google.client_secret.clone(),
),
));
tracing::info!("OAuth provider registered: google");
}
步骤三:更新用户名生成策略
ensure_unique_username 中的 fallback 前缀需要支持 google:
async fn ensure_unique_username(pool: &crate::db::Pool, base: &str, provider: &str) -> AppResult<String> {
// ...
let prefixed = format!("{provider}_{username}");
// ...
}
5. 配置环境变量
编辑 .env:
# Google OAuth
OAUTH_GOOGLE_CLIENT_ID=123456789-abcxxx.apps.googleusercontent.com
OAUTH_GOOGLE_CLIENT_SECRET=GOCSPX-xxxxxxxxxx
OAUTH_ENABLED=true已在 GitHub 配置时设置,无需重复。
6. 验证配置
重启后端,检查日志:
OAuth provider registered: github
OAuth provider registered: google
测试 API:
# 查看已配置 Provider
curl http://localhost:9000/api/v1/auth/oauth/providers
# 预期返回:
# {"code":0,"message":"操作成功","data":[
# {"name":"github","configured":true},
# {"name":"google","configured":true}
# ]}
# 测试 Google 授权 URL
curl -v http://localhost:9000/api/v1/auth/oauth/google 2>&1 | grep "Location:"
# 应输出:
# Location: https://accounts.google.com/o/oauth2/v2/auth?client_id=...
7. 测试完整流程
7.1 发起 Google 登录
浏览器打开:
http://localhost:9000/api/v1/auth/oauth/google
预期:302 到 Google 登录页面
7.2 Google 授权
- 选择 Google 账号
- 如果是 Testing 模式,会看到 "This app isn't verified" 警告
- 点击 Advanced → Go to raisfast (unsafe)
- 点击 Continue 授权
7.3 回调处理
Google 回调到:
http://localhost:9000/api/v1/auth/oauth/google/callback?code=xxx&state=xxx&scope=email+profile+openid
后端处理后 302 到前端:
http://localhost:3000/auth/callback?access_token=...&refresh_token=...&expires_in=900
7.4 前端登录页添加 Google 按钮
<a href={`${API_BASE}/auth/oauth/google`}>
<GoogleIcon />
Google 登录
</a>
8. 常见问题
"This app isn't verified" 警告
原因:应用处于 Testing 状态,且请求了非敏感 scope。
解决(开发阶段):
- 确保你的邮箱已添加到 Test Users
- 点击 Advanced → Go to raisfast (unsafe) 即可继续
解决(生产阶段):
- 在 OAuth consent screen 页面点击 PUBLISH APP
Error 400: redirect_uri_mismatch
原因:Google OAuth App 中配置的 redirect URI 与实际不匹配。
检查:
- Google Console → Credentials → 你的 OAuth client → Authorized redirect URIs
- 确保包含
http://localhost:9000/api/v1/auth/oauth/google/callback - 注意尾部斜杠、协议、端口号必须完全一致
Error 401: invalid_client
原因:Client ID 或 Client Secret 不正确。
解决:
- 复制 Google Console 中的完整 Client ID(很长的字符串)
- 确认
.env中没有多余空格或换行
获取不到邮箱
检查:
- OAuth consent screen 中是否添加了
emailscope - 授权 URL 中
scope=openid+email+profile是否完整 - Google 账号是否设置了邮箱
用户名冲突
如果 Google 用户名与已有用户冲突,系统自动追加前缀:
alice → google_alice → google_alice_a3f8
9. 生产环境部署
9.1 更新 Google OAuth App
| 配置项 | 添加值 |
|---|---|
| Authorized JavaScript origins | https://yourdomain.com |
| Authorized redirect URIs | https://yourdomain.com/api/v1/auth/oauth/google/callback |
9.2 发布应用
- OAuth consent screen → PUBLISH APP
- 如果 scope 中包含敏感 scope,需要提交 Google 审核(通常 1-3 天)
9.3 隐私政策页面
Google 要求应用提供隐私政策链接:
- 在前端创建
/privacy页面 - 在 OAuth consent screen 中添加 Privacy policy URL
9.4 环境变量
OAUTH_GOOGLE_CLIENT_ID=your-production-client-id
OAUTH_GOOGLE_CLIENT_SECRET=your-production-client-secret
10. Google vs GitHub 对比
| 对比项 | GitHub | |
|---|---|---|
| 创建凭证 | Settings → Developer settings | Google Cloud Console |
| 凭证数量 | Client ID + Secret(同页面) | Client ID + Secret(同页面) |
| 回调 URL 配置 | 创建时设置,可修改 | 创建时设置,可修改 |
| Scope | user:email |
openid email profile |
| 邮箱获取 | 可能需要额外 API 调用 | 直接返回 |
| 用户唯一标识 | id(数字) |
sub(数字) |
| 头像 | avatar_url |
picture |
| 昵称 | login |
name 或 given_name |
| 应用审核 | 不需要 | 敏感 scope 需要审核 |
| 测试模式 | 无 | Testing 模式限制用户 |
| Consent screen | 简单 | 复杂(需完整配置) |