完善google social,添加公钥过期时间

This commit is contained in:
李运家 2024-05-29 15:55:14 +08:00
parent 4e0c5fbfdc
commit 09809fd826
5 changed files with 87 additions and 64 deletions

1
Cargo.lock generated
View File

@ -1066,6 +1066,7 @@ dependencies = [
"http-body", "http-body",
"http-body-util", "http-body-util",
"jsonwebtoken", "jsonwebtoken",
"lazy_static",
"moka", "moka",
"once_cell", "once_cell",
"reqwest", "reqwest",

View File

@ -40,3 +40,4 @@ reqwest = "0.12"
futures-executor = "0.3" futures-executor = "0.3"
error-stack = "0.4" error-stack = "0.4"
jsonwebtoken = "9.3.0" jsonwebtoken = "9.3.0"
lazy_static = "1.4.0"

View File

@ -27,3 +27,4 @@ futures-util = { workspace = true }
jsonwebtoken = { workspace = true } jsonwebtoken = { workspace = true }
reqwest = { workspace = true, features = ["blocking", "json"] } reqwest = { workspace = true, features = ["blocking", "json"] }
validator = { workspace = true } validator = { workspace = true }
lazy_static = { workspace = true }

View File

@ -1,7 +1,9 @@
use std::collections::HashMap; use std::{collections::HashMap, sync::Arc};
use chrono::Utc; use chrono::Utc;
use futures_util::lock::Mutex;
use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation}; use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation};
use lazy_static::lazy_static;
use reqwest::Client; use reqwest::Client;
use serde::Deserialize; use serde::Deserialize;
use serde_json::Value; use serde_json::Value;
@ -10,6 +12,15 @@ use crate::resp::response::ResErr;
use super::SocialResult; use super::SocialResult;
lazy_static! {
pub static ref GOOGLE_SOCIAL: GoogleSocial = GoogleSocial::default();
}
#[derive(Default)]
pub struct GoogleSocial {
google_public_keys: Arc<Mutex<GooglePublicKeys>>,
}
// 假设GOOGLE_PUBLIC_CERT_URL是Google提供的公钥URL // 假设GOOGLE_PUBLIC_CERT_URL是Google提供的公钥URL
const GOOGLE_PUBLIC_CERT_URL: &str = "https://www.googleapis.com/oauth2/v3/certs"; const GOOGLE_PUBLIC_CERT_URL: &str = "https://www.googleapis.com/oauth2/v3/certs";
@ -93,7 +104,7 @@ impl From<Value> for GoogleJwtProfile {
} }
} }
#[derive(Deserialize, Debug, Clone)] #[derive(Deserialize, Debug, Clone, Default)]
struct GooglePublicKey { struct GooglePublicKey {
e: String, e: String,
n: String, n: String,
@ -103,20 +114,28 @@ struct GooglePublicKey {
kid: String, kid: String,
} }
#[derive(Deserialize, Debug, Clone)] #[derive(Deserialize, Debug, Clone, Default)]
struct GooglePublicKeys { struct GooglePublicKeys {
keys: Vec<GooglePublicKey>, keys: Vec<GooglePublicKey>,
refresh_at: i64,
} }
// 异步获取并解析Google公钥 impl GoogleSocial {
async fn fetch_and_parse_google_public_keys() -> SocialResult<HashMap<String, GooglePublicKey>> { // 异步获取并解析Google公钥
async fn fetch_and_parse_google_public_keys(&self) -> SocialResult<HashMap<String, GooglePublicKey>>
{
let mut public_keys = self.google_public_keys.lock().await;
if public_keys.keys.is_empty() || public_keys.refresh_at < Utc::now().timestamp() {
let response = Client::new().get(GOOGLE_PUBLIC_CERT_URL).send().await?; let response = Client::new().get(GOOGLE_PUBLIC_CERT_URL).send().await?;
let google_keys: GooglePublicKeys = response.json().await?; let mut google_keys: GooglePublicKeys = response.json().await?;
tracing::info!("Google公钥获取成功, {:?}", google_keys); tracing::info!("Google公钥获取成功, {:?}", google_keys);
google_keys.refresh_at = Utc::now().timestamp() + 3600;
*public_keys = google_keys;
}
let mut key_map = HashMap::new(); let mut key_map = HashMap::new();
// 解析公钥 // 解析公钥
for key in google_keys.keys.iter() { for key in public_keys.keys.iter() {
if key.kty == "RSA" { if key.kty == "RSA" {
key_map.insert(key.kid.to_owned(), key.to_owned()); key_map.insert(key.kid.to_owned(), key.to_owned());
} }
@ -124,12 +143,12 @@ async fn fetch_and_parse_google_public_keys() -> SocialResult<HashMap<String, Go
tracing::info!("Google公钥解析成功, {:?}", key_map); tracing::info!("Google公钥解析成功, {:?}", key_map);
Ok(key_map) Ok(key_map)
} }
// 验证ID Token并考虑kid匹配 // 验证ID Token并考虑kid匹配
pub async fn verify_id_token(id_token: &str) -> SocialResult<GoogleJwtProfile> { pub async fn verify_id_token(&self, id_token: &str) -> SocialResult<GoogleJwtProfile> {
// 获取并解析公钥 // 获取并解析公钥
let public_keys = fetch_and_parse_google_public_keys().await?; let public_keys = self.fetch_and_parse_google_public_keys().await?;
// 解码Token头部以获取kid // 解码Token头部以获取kid
let token_header = jsonwebtoken::decode_header(id_token).unwrap(); let token_header = jsonwebtoken::decode_header(id_token).unwrap();
@ -168,4 +187,5 @@ pub async fn verify_id_token(id_token: &str) -> SocialResult<GoogleJwtProfile> {
} }
Ok(google_jwt_profile) Ok(google_jwt_profile)
}
} }

View File

@ -4,10 +4,10 @@ use domain::entities::account::Account;
use library::{db, token}; use library::{db, token};
use library::resp::response::ResErr::ErrPerm; use library::resp::response::ResErr::ErrPerm;
use library::resp::response::{ResErr, ResOK, ResResult}; use library::resp::response::{ResErr, ResOK, ResResult};
use library::social::google; use library::social::google::GOOGLE_SOCIAL;
pub async fn authticate_google(req: AuthenticateGooleAccountReq) -> ResResult<ResOK<(String, String)>> { pub async fn authticate_google(req: AuthenticateGooleAccountReq) -> ResResult<ResOK<(String, String)>> {
let verify_result = google::verify_id_token(&req.id_token.unwrap()).await.map_err(|err| { let verify_result = GOOGLE_SOCIAL.verify_id_token(&req.id_token.unwrap()).await.map_err(|err| {
tracing::error!(error = ?err, "校验Google Token失败"); tracing::error!(error = ?err, "校验Google Token失败");
ErrPerm(None) ErrPerm(None)
})?; })?;