添加apple id token校验
This commit is contained in:
parent
35b6698d07
commit
22f7649bca
@ -0,0 +1,193 @@
|
|||||||
|
use std::{collections::HashMap, sync::Arc};
|
||||||
|
|
||||||
|
use chrono::Utc;
|
||||||
|
use futures_util::lock::Mutex;
|
||||||
|
use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation};
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use reqwest::Client;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
|
use crate::resp::response::ResErr;
|
||||||
|
|
||||||
|
use super::SocialResult;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
pub static ref APPLE_SOCIAL: AppleSocial = AppleSocial::default();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct AppleSocial {
|
||||||
|
apple_public_keys: Arc<Mutex<ApplePublicKeys>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 假设APPLE_PUBLIC_CERT_URL是Apple提供的公钥URL
|
||||||
|
const APPLE_PUBLIC_CERT_URL: &str = "https://appleid.apple.com/auth/keys";
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct AppleJwtProfile {
|
||||||
|
// iss (issuer):签发人
|
||||||
|
pub iss: String,
|
||||||
|
// sub (subject):主题
|
||||||
|
pub sub: String,
|
||||||
|
pub azp: String,
|
||||||
|
// aud (audience):受众
|
||||||
|
pub aud: String,
|
||||||
|
// iat (Issued At):签发时间
|
||||||
|
pub iat: i64,
|
||||||
|
// exp (expiration time):过期时间
|
||||||
|
pub exp: i64,
|
||||||
|
pub email: String,
|
||||||
|
pub email_verified: bool,
|
||||||
|
pub at_hash: String,
|
||||||
|
pub name: String,
|
||||||
|
pub picture: String,
|
||||||
|
pub given_name: String,
|
||||||
|
pub family_name: String,
|
||||||
|
pub locale: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AppleJwtProfile {
|
||||||
|
fn new() -> Self {
|
||||||
|
AppleJwtProfile {
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Value> for AppleJwtProfile {
|
||||||
|
fn from(value: Value) -> Self {
|
||||||
|
let mut apple_jwt_profile = AppleJwtProfile::new();
|
||||||
|
if let Some(value) = value.get("iss") {
|
||||||
|
apple_jwt_profile.iss = value.as_str().unwrap().to_string();
|
||||||
|
}
|
||||||
|
if let Some(value) = value.get("sub") {
|
||||||
|
apple_jwt_profile.sub = value.as_str().unwrap().to_string();
|
||||||
|
}
|
||||||
|
if let Some(value) = value.get("azp") {
|
||||||
|
apple_jwt_profile.azp = value.as_str().unwrap().to_string();
|
||||||
|
}
|
||||||
|
if let Some(value) = value.get("aud") {
|
||||||
|
apple_jwt_profile.aud = value.as_str().unwrap().to_string();
|
||||||
|
}
|
||||||
|
if let Some(value) = value.get("iat") {
|
||||||
|
apple_jwt_profile.iat = value.as_i64().unwrap_or_default();
|
||||||
|
}
|
||||||
|
if let Some(value) = value.get("exp") {
|
||||||
|
apple_jwt_profile.exp = value.as_i64().unwrap_or_default();
|
||||||
|
}
|
||||||
|
if let Some(value) = value.get("email") {
|
||||||
|
apple_jwt_profile.email = value.as_str().unwrap().to_string();
|
||||||
|
}
|
||||||
|
if let Some(value) = value.get("email_verified") {
|
||||||
|
apple_jwt_profile.email_verified = value.as_bool().unwrap_or_default();
|
||||||
|
}
|
||||||
|
if let Some(value) = value.get("at_hash") {
|
||||||
|
apple_jwt_profile.at_hash = value.as_str().unwrap().to_string();
|
||||||
|
}
|
||||||
|
if let Some(value) = value.get("name") {
|
||||||
|
apple_jwt_profile.name = value.as_str().unwrap().to_string();
|
||||||
|
}
|
||||||
|
if let Some(value) = value.get("picture") {
|
||||||
|
apple_jwt_profile.picture = value.as_str().unwrap().to_string();
|
||||||
|
}
|
||||||
|
if let Some(value) = value.get("given_name") {
|
||||||
|
apple_jwt_profile.given_name = value.as_str().unwrap().to_string();
|
||||||
|
}
|
||||||
|
if let Some(value) = value.get("family_name") {
|
||||||
|
apple_jwt_profile.family_name = value.as_str().unwrap().to_string();
|
||||||
|
}
|
||||||
|
if let Some(value) = value.get("locale") {
|
||||||
|
apple_jwt_profile.locale = value.as_str().unwrap().to_string();
|
||||||
|
}
|
||||||
|
apple_jwt_profile
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug, Clone, Default)]
|
||||||
|
struct ApplePublicKey {
|
||||||
|
e: String,
|
||||||
|
n: String,
|
||||||
|
kty: String,
|
||||||
|
#[serde(rename = "use")]
|
||||||
|
use_: String,
|
||||||
|
alg: String,
|
||||||
|
kid: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug, Clone, Default)]
|
||||||
|
struct ApplePublicKeys {
|
||||||
|
keys: Vec<ApplePublicKey>,
|
||||||
|
#[serde(skip)]
|
||||||
|
refresh_at: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AppleSocial {
|
||||||
|
// 异步获取并解析Apple公钥
|
||||||
|
async fn fetch_and_parse_apple_public_keys(&self) -> SocialResult<HashMap<String, ApplePublicKey>>
|
||||||
|
{
|
||||||
|
let mut public_keys = self.apple_public_keys.lock().await;
|
||||||
|
if public_keys.keys.is_empty() || public_keys.refresh_at < Utc::now().timestamp() {
|
||||||
|
let response = Client::new().get(APPLE_PUBLIC_CERT_URL).send().await?;
|
||||||
|
let mut apple_keys: ApplePublicKeys = response.json().await?;
|
||||||
|
tracing::info!("Apple公钥获取成功, {:?}", apple_keys);
|
||||||
|
apple_keys.refresh_at = Utc::now().timestamp() + 3600;
|
||||||
|
*public_keys = apple_keys;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut key_map = HashMap::new();
|
||||||
|
// 解析公钥
|
||||||
|
for key in public_keys.keys.iter() {
|
||||||
|
if key.kty == "RSA" {
|
||||||
|
key_map.insert(key.kid.to_owned(), key.to_owned());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tracing::info!("Apple公钥解析成功, {:?}", key_map);
|
||||||
|
Ok(key_map)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证ID Token并考虑kid匹配
|
||||||
|
pub async fn verify_id_token(&self, id_token: &str) -> SocialResult<AppleJwtProfile> {
|
||||||
|
// 获取并解析公钥
|
||||||
|
let public_keys = self.fetch_and_parse_apple_public_keys().await?;
|
||||||
|
|
||||||
|
// 解码Token头部以获取kid
|
||||||
|
let token_header = jsonwebtoken::decode_header(id_token).unwrap();
|
||||||
|
let kid = token_header.kid;
|
||||||
|
|
||||||
|
// 检查是否找到了有效的kid
|
||||||
|
if kid.is_none() {
|
||||||
|
return Err(Box::new(ResErr::social("校验Token失败,未找到有效的kid")));
|
||||||
|
}
|
||||||
|
let kid = kid.unwrap();
|
||||||
|
|
||||||
|
// 根据kid找到正确的公钥
|
||||||
|
let key = public_keys
|
||||||
|
.get(&kid)
|
||||||
|
.ok_or_else(|| Box::new(ResErr::social("校验Token失败,未找到正确的公钥")))?;
|
||||||
|
|
||||||
|
tracing::info!("public key : {:?}", 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 claims: Value = decoded.claims;
|
||||||
|
let apple_jwt_profile = AppleJwtProfile::from(claims);
|
||||||
|
|
||||||
|
// 校验有效期
|
||||||
|
if apple_jwt_profile.exp < Utc::now().timestamp() {
|
||||||
|
return Err(Box::new(ResErr::social("校验Token失败,token有效期无效")));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(apple_jwt_profile)
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +1,14 @@
|
|||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use domain::dto::account::AuthenticateGooleAccountReq;
|
use domain::dto::account::AuthenticateGooleAccountReq;
|
||||||
use domain::entities::account::Account;
|
use domain::entities::account::Account;
|
||||||
|
use library::social::apple::APPLE_SOCIAL;
|
||||||
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::GOOGLE_SOCIAL;
|
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_SOCIAL.verify_id_token(&req.id_token.unwrap()).await.map_err(|err| {
|
let verify_result = APPLE_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)
|
||||||
})?;
|
})?;
|
||||||
|
Loading…
Reference in New Issue
Block a user