修改google play登录
This commit is contained in:
parent
0ce2c57103
commit
6f04f32161
107
Cargo.lock
generated
107
Cargo.lock
generated
@ -1,6 +1,6 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "addr2line"
|
||||
@ -264,6 +264,12 @@ version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "cfg_aliases"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.39"
|
||||
@ -1022,6 +1028,7 @@ dependencies = [
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
"tower-service",
|
||||
"webpki-roots",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1368,6 +1375,7 @@ dependencies = [
|
||||
"lazy_static",
|
||||
"macro",
|
||||
"moka",
|
||||
"oauth2",
|
||||
"once_cell",
|
||||
"redis",
|
||||
"reqwest",
|
||||
@ -1673,6 +1681,26 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "oauth2"
|
||||
version = "5.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51e219e79014df21a225b1860a479e2dcd7cbd9130f4defd4bd0e191ea31d67d"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"chrono",
|
||||
"getrandom",
|
||||
"http",
|
||||
"rand",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_path_to_error",
|
||||
"sha2",
|
||||
"thiserror 1.0.69",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.36.7"
|
||||
@ -1932,6 +1960,58 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quinn"
|
||||
version = "0.11.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62e96808277ec6f97351a2380e6c25114bc9e67037775464979f3037c92d05ef"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"pin-project-lite",
|
||||
"quinn-proto",
|
||||
"quinn-udp",
|
||||
"rustc-hash",
|
||||
"rustls",
|
||||
"socket2",
|
||||
"thiserror 2.0.11",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quinn-proto"
|
||||
version = "0.11.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2fe5ef3495d7d2e377ff17b1a8ce2ee2ec2a18cde8b6ad6619d65d0701c135d"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"getrandom",
|
||||
"rand",
|
||||
"ring",
|
||||
"rustc-hash",
|
||||
"rustls",
|
||||
"rustls-pki-types",
|
||||
"slab",
|
||||
"thiserror 2.0.11",
|
||||
"tinyvec",
|
||||
"tracing",
|
||||
"web-time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quinn-udp"
|
||||
version = "0.5.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c40286217b4ba3a71d644d752e6a0b71f13f1b6a2c5311acfcbe0c2418ed904"
|
||||
dependencies = [
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"socket2",
|
||||
"tracing",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.38"
|
||||
@ -2078,7 +2158,10 @@ dependencies = [
|
||||
"once_cell",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"quinn",
|
||||
"rustls",
|
||||
"rustls-pemfile",
|
||||
"rustls-pki-types",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
@ -2086,12 +2169,14 @@ dependencies = [
|
||||
"system-configuration",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
"tokio-rustls",
|
||||
"tower",
|
||||
"tower-service",
|
||||
"url",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
"webpki-roots",
|
||||
"windows-registry",
|
||||
]
|
||||
|
||||
@ -2136,6 +2221,12 @@ version = "0.1.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497"
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.4.1"
|
||||
@ -2165,6 +2256,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f287924602bf649d949c63dc8ac8b235fa5387d394020705b80c4eb597ce5b8"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
"rustls-webpki",
|
||||
"subtle",
|
||||
@ -2185,6 +2277,9 @@ name = "rustls-pki-types"
|
||||
version = "1.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2bf47e6ff922db3825eb750c4e2ff784c6ff8fb9e13046ef6a1d1c5401b0b37"
|
||||
dependencies = [
|
||||
"web-time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-webpki"
|
||||
@ -3315,6 +3410,7 @@ dependencies = [
|
||||
"form_urlencoded",
|
||||
"idna 1.0.3",
|
||||
"percent-encoding",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3530,6 +3626,15 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "0.26.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d642ff16b7e79272ae451b7322067cdc17cadf68c23264be9d94a32319efe7e"
|
||||
dependencies = [
|
||||
"rustls-pki-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "whoami"
|
||||
version = "1.5.2"
|
||||
|
@ -62,3 +62,4 @@ redis = "0.27.6"
|
||||
deadpool-redis = "0.18.0"
|
||||
chrono-tz = "0.10.0"
|
||||
inventory = "0.3.17"
|
||||
oauth2 = "5.0.0"
|
@ -40,6 +40,7 @@ redis = { workspace = true, features = ["tokio-comp", "json"] }
|
||||
deadpool-redis = { workspace = true }
|
||||
chrono-tz = { workspace = true }
|
||||
inventory = { workspace = true }
|
||||
oauth2 = { workspace = true, features = ["reqwest"]}
|
||||
|
||||
domain = { path = "../domain" }
|
||||
i18n = { path = "../i18n" }
|
||||
|
@ -62,11 +62,17 @@ pub struct Redis {
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct Social {
|
||||
pub wechat: Wechat,
|
||||
// pub google: google::GoogleSocial,
|
||||
pub google: Google,
|
||||
// pub facebook: facebook::FacebookSocial,
|
||||
// pub apple: apple::AppleSocial,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct Google {
|
||||
pub client_id: String,
|
||||
pub client_secret: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct Wechat {
|
||||
pub app_id: String,
|
||||
|
@ -4,10 +4,17 @@ use chrono::Utc;
|
||||
use futures_util::lock::Mutex;
|
||||
use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation};
|
||||
use lazy_static::lazy_static;
|
||||
use oauth2::basic::BasicClient;
|
||||
use oauth2::{reqwest, RevocationUrl};
|
||||
use oauth2::{
|
||||
AuthUrl, AuthorizationCode, ClientId, ClientSecret, CsrfToken, PkceCodeChallenge, RedirectUrl,
|
||||
Scope, TokenResponse, TokenUrl,
|
||||
};
|
||||
use reqwest::Client;
|
||||
use serde::Deserialize;
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::config;
|
||||
use crate::model::response::ResErr;
|
||||
|
||||
use super::SocialResult;
|
||||
@ -133,93 +140,110 @@ impl GoogleSocial {
|
||||
|
||||
// 根据kid找到正确的公钥
|
||||
let key = public_keys
|
||||
.get(&kid)
|
||||
.ok_or_else(|| Box::new(ResErr::social("校验Token失败,未找到正确的公钥")))?;
|
||||
.get(&kid);
|
||||
|
||||
tracing::debug!("public key : {:?}", key);
|
||||
// TODO:
|
||||
// 如果是google game account,可能找不到正确的公钥,需要使用oauth2开发库来实现相应的校验
|
||||
// oauth2开发库参考https://github.com/ramosbugs/oauth2-rs/blob/main/examples/google.rs
|
||||
// google game account账户校验参考:https://github.com/heroiclabs/nakama/blob/master/social/social.go
|
||||
|
||||
// 验证Token
|
||||
let mut validation: Validation = Validation::new(Algorithm::RS256);
|
||||
validation.set_issuer(&["https://accounts.google.com", "accounts.google.com"]); // 设置预期的发行者
|
||||
validation.validate_aud = false;
|
||||
match key {
|
||||
Some(key) => {
|
||||
// 验证Token
|
||||
let mut validation: Validation = Validation::new(Algorithm::RS256);
|
||||
validation.set_issuer(&["https://accounts.google.com", "accounts.google.com"]); // 设置预期的发行者
|
||||
validation.validate_aud = false;
|
||||
|
||||
let decoded = decode::<Value>(
|
||||
id_token,
|
||||
&DecodingKey::from_rsa_components(key.n.as_str(), key.e.as_str()).unwrap(),
|
||||
&validation,
|
||||
)?;
|
||||
let decoded = decode::<Value>(
|
||||
id_token,
|
||||
&DecodingKey::from_rsa_components(key.n.as_str(), key.e.as_str()).unwrap(),
|
||||
&validation,
|
||||
)?;
|
||||
|
||||
let claims: Value = decoded.claims;
|
||||
let google_jwt_profile = GoogleJwtProfile::from(claims);
|
||||
let claims: Value = decoded.claims;
|
||||
let google_jwt_profile = GoogleJwtProfile::from(claims);
|
||||
|
||||
// 校验有效期
|
||||
if google_jwt_profile.exp < Utc::now().timestamp() {
|
||||
return Err(Box::new(ResErr::social("校验Token失败,token有效期无效")));
|
||||
// 校验有效期
|
||||
if google_jwt_profile.exp < Utc::now().timestamp() {
|
||||
return Err(Box::new(ResErr::social("校验Token失败,token有效期无效")));
|
||||
}
|
||||
|
||||
Ok(google_jwt_profile)
|
||||
}
|
||||
None => {
|
||||
self.verify_play_games_token(id_token).await
|
||||
}
|
||||
}
|
||||
|
||||
Ok(google_jwt_profile)
|
||||
}
|
||||
|
||||
/// 验证 Google Play Games 的身份信息
|
||||
/// 参考: https://developers.google.com/games/services/android/signin
|
||||
pub async fn verify_play_games_token(&self, play_games_token: &str) -> SocialResult<GoogleJwtProfile> {
|
||||
// Google Play Games 的验证端点
|
||||
const GOOGLE_PLAY_GAMES_TOKEN_INFO_URL: &str =
|
||||
"https://oauth2.googleapis.com/tokeninfo";
|
||||
/// 使用 OAuth2 token exchange 获取有效的 access token
|
||||
/// oauth2开发库参考https://github.com/ramosbugs/oauth2-rs/blob/main/examples/google.rs
|
||||
/// google game account账户校验参考:https://github.com/heroiclabs/nakama/blob/master/social/social.go
|
||||
/// TODO: redirect_uri 需要改为实际的回调地址
|
||||
pub async fn verify_play_games_token(
|
||||
&self,
|
||||
play_games_token: &str,
|
||||
) -> SocialResult<GoogleJwtProfile> {
|
||||
// 1. 创建 OAuth2 client
|
||||
let client = BasicClient::new(ClientId::new(config!().social.google.client_id.clone()))
|
||||
.set_client_secret(ClientSecret::new(config!().social.google.client_secret.clone()))
|
||||
.set_auth_uri(AuthUrl::new(
|
||||
"https://accounts.google.com/o/oauth2/v2/auth".to_string(),
|
||||
)?)
|
||||
.set_token_uri(TokenUrl::new(
|
||||
"https://oauth2.googleapis.com/token".to_string(),
|
||||
)?)
|
||||
.set_redirect_uri(RedirectUrl::new(
|
||||
"http://localhost:8080/auth/google/callback".to_string(),
|
||||
)?)
|
||||
// Google supports OAuth 2.0 Token Revocation (RFC-7009)
|
||||
.set_revocation_url(
|
||||
RevocationUrl::new("https://oauth2.googleapis.com/revoke".to_string())
|
||||
.expect("Invalid revocation endpoint URL"),
|
||||
);
|
||||
|
||||
let client = Client::new();
|
||||
let response = client
|
||||
.get(GOOGLE_PLAY_GAMES_TOKEN_INFO_URL)
|
||||
.query(&[("access_token", play_games_token)])
|
||||
.send()
|
||||
// Google supports Proof Key for Code Exchange (PKCE - https://oauth.net/2/pkce/).
|
||||
// Create a PKCE code verifier and SHA-256 encode it as a code challenge.
|
||||
let (pkce_code_challenge, pkce_code_verifier) = PkceCodeChallenge::new_random_sha256();
|
||||
|
||||
// Generate the authorization URL to which we'll redirect the user.
|
||||
let (_authorize_url, _csrf_state) = client
|
||||
.authorize_url(CsrfToken::new_random)
|
||||
// This example is requesting access to the "calendar" features and the user's profile.
|
||||
.add_scope(Scope::new(
|
||||
"https://www.googleapis.com/auth/calendar".to_string(),
|
||||
))
|
||||
.add_scope(Scope::new(
|
||||
"https://www.googleapis.com/auth/plus.me".to_string(),
|
||||
))
|
||||
.set_pkce_challenge(pkce_code_challenge)
|
||||
.url();
|
||||
|
||||
// 2. 执行 token exchange
|
||||
let token_response = client
|
||||
.exchange_code(AuthorizationCode::new(play_games_token.to_string()))
|
||||
.set_pkce_verifier(pkce_code_verifier)
|
||||
.request_async(&reqwest::Client::new())
|
||||
.await?;
|
||||
|
||||
if !response.status().is_success() {
|
||||
return Err(Box::new(ResErr::social("验证 Google Play Games Token 失败")));
|
||||
let access_token = token_response.access_token();
|
||||
|
||||
// 3. 使用新的 access token 获取用户信息
|
||||
let mut profile = self.fetch_play_games_profile(access_token.secret()).await?;
|
||||
|
||||
// 4. 获取额外的用户信息(如果需要)
|
||||
if let Some(additional_info) = self
|
||||
.fetch_additional_player_info(access_token.secret())
|
||||
.await?
|
||||
{
|
||||
profile.google_play_games_profile = Some(serde_json::to_string(&additional_info)?);
|
||||
}
|
||||
|
||||
let token_info: Value = response.json().await?;
|
||||
|
||||
// 验证 token 信息
|
||||
if let Some(error) = token_info.get("error") {
|
||||
return Err(Box::new(ResErr::social(format!(
|
||||
"Google Play Games Token 无效: {}",
|
||||
error
|
||||
))));
|
||||
}
|
||||
|
||||
// 验证 audience 是否匹配你的应用
|
||||
if let Some(aud) = token_info.get("aud") {
|
||||
// TODO: 验证 aud 是否匹配你的 Google Play Games 应用 ID
|
||||
tracing::debug!("Google Play Games aud: {}", aud);
|
||||
}
|
||||
|
||||
// 构建用户资料
|
||||
let mut profile = GoogleJwtProfile::default();
|
||||
if let Some(sub) = token_info.get("sub").and_then(|v| v.as_str()) {
|
||||
profile.sub = sub.to_string();
|
||||
}
|
||||
if let Some(email) = token_info.get("email").and_then(|v| v.as_str()) {
|
||||
profile.email = email.to_string();
|
||||
}
|
||||
|
||||
// 获取 Google Play Games 特定的信息
|
||||
let play_games_info = self.fetch_play_games_profile(play_games_token).await?;
|
||||
profile.google_play_games_id = play_games_info.get("playerId")
|
||||
.and_then(|v| v.as_str())
|
||||
.map(String::from);
|
||||
|
||||
Ok(profile)
|
||||
}
|
||||
|
||||
/// 获取 Google Play Games 用户资料
|
||||
async fn fetch_play_games_profile(&self, access_token: &str) -> SocialResult<Value> {
|
||||
const PLAY_GAMES_PROFILE_URL: &str =
|
||||
"https://games.googleapis.com/games/v1/players/me";
|
||||
async fn fetch_play_games_profile(&self, access_token: &str) -> SocialResult<GoogleJwtProfile> {
|
||||
const PLAY_GAMES_PROFILE_URL: &str = "https://games.googleapis.com/games/v1/players/me";
|
||||
|
||||
let client = Client::new();
|
||||
let response = client
|
||||
@ -229,10 +253,56 @@ impl GoogleSocial {
|
||||
.await?;
|
||||
|
||||
if !response.status().is_success() {
|
||||
return Err(Box::new(ResErr::social("获取 Google Play Games 用户资料失败")));
|
||||
return Err(Box::new(ResErr::social(
|
||||
"获取 Google Play Games 用户资料失败",
|
||||
)));
|
||||
}
|
||||
|
||||
let player_info: Value = response.json().await?;
|
||||
|
||||
// 构建用户资料
|
||||
let mut profile = GoogleJwtProfile::default();
|
||||
|
||||
// 设置基本信息
|
||||
if let Some(player_id) = player_info.get("playerId").and_then(|v| v.as_str()) {
|
||||
profile.sub = player_id.to_string();
|
||||
profile.google_play_games_id = Some(player_id.to_string());
|
||||
}
|
||||
|
||||
// 设置名称
|
||||
if let Some(display_name) = player_info.get("displayName").and_then(|v| v.as_str()) {
|
||||
profile.name = display_name.to_string();
|
||||
}
|
||||
|
||||
// 设置头像
|
||||
if let Some(avatar_image_url) = player_info.get("avatarImageUrl").and_then(|v| v.as_str()) {
|
||||
profile.picture = avatar_image_url.to_string();
|
||||
}
|
||||
|
||||
let profile: Value = response.json().await?;
|
||||
Ok(profile)
|
||||
}
|
||||
|
||||
/// 获取额外的玩家信息
|
||||
async fn fetch_additional_player_info(
|
||||
&self,
|
||||
access_token: &str,
|
||||
) -> SocialResult<Option<Value>> {
|
||||
const PLAY_GAMES_PLAYER_STATS_URL: &str =
|
||||
"https://games.googleapis.com/games/v1/players/me/playerStates";
|
||||
|
||||
let client = Client::new();
|
||||
let response = client
|
||||
.get(PLAY_GAMES_PLAYER_STATS_URL)
|
||||
.header("Authorization", format!("Bearer {}", access_token))
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
if !response.status().is_success() {
|
||||
tracing::warn!("获取额外玩家信息失败: {}", response.status());
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let stats: Value = response.json().await?;
|
||||
Ok(Some(stats))
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user