增加google账户信息
This commit is contained in:
parent
cbfc70357e
commit
638226a22e
36
library/src/errors.rs
Normal file
36
library/src/errors.rs
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
use core::fmt;
|
||||||
|
use std::error::Error;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct MessageError(Option<String>);
|
||||||
|
|
||||||
|
// 实现Error trait
|
||||||
|
impl Error for MessageError {
|
||||||
|
|
||||||
|
fn cause(&self) -> Option<&dyn Error> {
|
||||||
|
self.source()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// 实现Display trait,这是Error trait的一部分要求
|
||||||
|
impl fmt::Display for MessageError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match &self.0 {
|
||||||
|
Some(message) => write!(f, "{}", message),
|
||||||
|
None => write!(f, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<String> for MessageError {
|
||||||
|
fn from(value: String) -> Self {
|
||||||
|
MessageError(Some(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&str> for MessageError {
|
||||||
|
fn from(value: &str) -> Self {
|
||||||
|
MessageError(Some(value.to_string()))
|
||||||
|
}
|
||||||
|
}
|
@ -5,4 +5,5 @@ pub mod resp;
|
|||||||
pub mod middleware;
|
pub mod middleware;
|
||||||
pub mod token;
|
pub mod token;
|
||||||
pub mod cache;
|
pub mod cache;
|
||||||
pub mod social;
|
pub mod social;
|
||||||
|
pub mod errors;
|
@ -1,3 +1,5 @@
|
|||||||
|
use std::{error::Error as StdError, fmt::Display};
|
||||||
|
|
||||||
use axum::{
|
use axum::{
|
||||||
response::{IntoResponse, Response},
|
response::{IntoResponse, Response},
|
||||||
Json,
|
Json,
|
||||||
@ -22,6 +24,7 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub enum ResErr {
|
pub enum ResErr {
|
||||||
Error(i32, String),
|
Error(i32, String),
|
||||||
ErrParams(Option<String>),
|
ErrParams(Option<String>),
|
||||||
@ -31,6 +34,7 @@ pub enum ResErr {
|
|||||||
ErrSystem(Option<String>),
|
ErrSystem(Option<String>),
|
||||||
ErrData(Option<String>),
|
ErrData(Option<String>),
|
||||||
ErrService(Option<String>),
|
ErrService(Option<String>),
|
||||||
|
ErrSocial(Option<String>),
|
||||||
}
|
}
|
||||||
|
|
||||||
use ResErr::*;
|
use ResErr::*;
|
||||||
@ -95,10 +99,81 @@ impl IntoResponse for ResErr {
|
|||||||
None => Status::<()>::Err(code, String::from("服务异常")),
|
None => Status::<()>::Err(code, String::from("服务异常")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ErrSocial(msg) => {
|
||||||
|
let code = 80000;
|
||||||
|
match msg {
|
||||||
|
Some(v) => Status::<()>::Err(code, v),
|
||||||
|
None => Status::<()>::Err(code, String::from("社交服务异常")),
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Json(status.to_reply()).into_response()
|
Json(status.to_reply()).into_response()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// impl Display for ResErr {
|
||||||
|
// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
// // write!(f, "{:?}", self.message)
|
||||||
|
// match self {
|
||||||
|
// Error(value, message) => write!(f, "{} - {:?}", value, message),
|
||||||
|
// ErrParams(message)
|
||||||
|
// | ErrAuth(message)
|
||||||
|
// | ErrPerm(message)
|
||||||
|
// | ErrNotFound(message)
|
||||||
|
// | ErrSystem(message)
|
||||||
|
// | ErrData(message)
|
||||||
|
// | ErrService(message)
|
||||||
|
// | ErrSocial(message) => write!(f, "{:?}", message),
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// impl StdError for ResErr {
|
||||||
|
// fn cause(&self) -> Option<&dyn StdError> {
|
||||||
|
// match self {
|
||||||
|
// Error(_, _) => None,
|
||||||
|
// ErrParams(_)
|
||||||
|
// | ErrAuth(_)
|
||||||
|
// | ErrPerm(_)
|
||||||
|
// | ErrNotFound(_)
|
||||||
|
// | ErrSystem(_)
|
||||||
|
// | ErrData(_)
|
||||||
|
// | ErrService(_)
|
||||||
|
// | ErrSocial(_) => None,
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// fn source(&self) -> Option<&(dyn StdError + 'static)> {
|
||||||
|
// None
|
||||||
|
// }
|
||||||
|
|
||||||
|
// fn description(&self) -> &str {
|
||||||
|
// match self {
|
||||||
|
// Error(value, message) => &format!("Error, {} - {}", value, message),
|
||||||
|
// ErrParams(message) => &format!("ErrParams, {}", message),
|
||||||
|
// ErrAuth(message) => &format!("ErrAuth, {}", message),
|
||||||
|
// ErrPerm(message) => &format!("ErrPerm, {}", message),
|
||||||
|
// ErrNotFound(message) => &format!("ErrNotFound, {}", message),
|
||||||
|
// ErrSystem(message) => &format!("ErrSystem, {}", message),
|
||||||
|
// ErrData(message) => &format!("ErrData, {}", message),
|
||||||
|
// ErrService(message) => &format!("ErrService, {}", message),
|
||||||
|
// ErrSocial(message) => &format!("ErrSocial, {}", message),
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// fn provide<'a>(&'a self, request: &mut std::error::Request<'a>) {
|
||||||
|
// match self {
|
||||||
|
// ResErr::ErrSystem(e) => request.provide_ref(e),
|
||||||
|
// ResErr::ErrData(e) => request.provide_ref(e),
|
||||||
|
// ResErr::ErrService(e) => request.provide_ref(e),
|
||||||
|
// ResErr::ErrSocial(e) => request.provide_ref(e),
|
||||||
|
// ResErr::ErrNotFound(e) => request.provide_ref(e),
|
||||||
|
// ResErr::ErrPerm(e) => request.provide_ref(e),
|
||||||
|
// ResErr::ErrAuth(e) => request.provide_ref(e),
|
||||||
|
// ResErr::ErrParams(e) => request.provide_ref(e),
|
||||||
|
// ResErr::Error(_, e) => request.provide_ref(e),
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
pub type ResResult<T> = std::result::Result<T, ResErr>;
|
pub type ResResult<T> = std::result::Result<T, ResErr>;
|
||||||
|
@ -1,14 +1,99 @@
|
|||||||
use std::collections::HashMap;
|
use std::{collections::HashMap, fmt::Error};
|
||||||
|
|
||||||
use jsonwebtoken::{decode, errors::{Error, ErrorKind}, Algorithm, DecodingKey, TokenData, Validation};
|
use chrono::Utc;
|
||||||
|
use jsonwebtoken::{decode, errors::ErrorKind, Algorithm, DecodingKey, TokenData, Validation};
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
type SocialResult<T> = Result<T, Box<dyn std::error::Error>>;
|
use crate::{errors::MessageError, resp::response::ResErr};
|
||||||
|
|
||||||
|
use super::SocialResult;
|
||||||
|
|
||||||
// 假设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";
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct GoogleJwtProfile {
|
||||||
|
// 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 GoogleJwtProfile {
|
||||||
|
fn new() -> Self {
|
||||||
|
GoogleJwtProfile{
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Value> for GoogleJwtProfile {
|
||||||
|
|
||||||
|
fn from(value: Value) -> Self {
|
||||||
|
let mut google_jwt_profile = GoogleJwtProfile::new();
|
||||||
|
if let Some(value) = value.get("iss") {
|
||||||
|
google_jwt_profile.iss = value.to_string();
|
||||||
|
}
|
||||||
|
if let Some(value) = value.get("sub") {
|
||||||
|
google_jwt_profile.sub = value.to_string();
|
||||||
|
}
|
||||||
|
if let Some(value) = value.get("azp") {
|
||||||
|
google_jwt_profile.azp = value.to_string();
|
||||||
|
}
|
||||||
|
if let Some(value) = value.get("aud") {
|
||||||
|
google_jwt_profile.aud = value.to_string();
|
||||||
|
}
|
||||||
|
if let Some(value) = value.get("iat") {
|
||||||
|
google_jwt_profile.iat = value.as_i64().unwrap_or_default();
|
||||||
|
}
|
||||||
|
if let Some(value) = value.get("exp") {
|
||||||
|
google_jwt_profile.exp = value.as_i64().unwrap_or_default();
|
||||||
|
}
|
||||||
|
if let Some(value) = value.get("email") {
|
||||||
|
google_jwt_profile.email = value.to_string();
|
||||||
|
}
|
||||||
|
if let Some(value) = value.get("email_verified") {
|
||||||
|
google_jwt_profile.email_verified = value.as_bool().unwrap_or_default();
|
||||||
|
}
|
||||||
|
if let Some(value) = value.get("at_hash") {
|
||||||
|
google_jwt_profile.at_hash = value.to_string();
|
||||||
|
}
|
||||||
|
if let Some(value) = value.get("name") {
|
||||||
|
google_jwt_profile.name = value.to_string();
|
||||||
|
}
|
||||||
|
if let Some(value) = value.get("picture") {
|
||||||
|
google_jwt_profile.picture = value.to_string();
|
||||||
|
}
|
||||||
|
if let Some(value) = value.get("given_name") {
|
||||||
|
google_jwt_profile.given_name = value.to_string();
|
||||||
|
}
|
||||||
|
if let Some(value) = value.get("family_name") {
|
||||||
|
google_jwt_profile.family_name = value.to_string();
|
||||||
|
}
|
||||||
|
if let Some(value) = value.get("locale") {
|
||||||
|
google_jwt_profile.locale = value.to_string();
|
||||||
|
}
|
||||||
|
google_jwt_profile
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// 异步获取并解析Google公钥
|
// 异步获取并解析Google公钥
|
||||||
async fn fetch_and_parse_google_public_keys() -> SocialResult<HashMap<String, String>> {
|
async fn fetch_and_parse_google_public_keys() -> SocialResult<HashMap<String, String>> {
|
||||||
let response = Client::new().get(GOOGLE_PUBLIC_CERT_URL).send().await?;
|
let response = Client::new().get(GOOGLE_PUBLIC_CERT_URL).send().await?;
|
||||||
@ -27,7 +112,7 @@ async fn fetch_and_parse_google_public_keys() -> SocialResult<HashMap<String, St
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 验证ID Token并考虑kid匹配
|
// 验证ID Token并考虑kid匹配
|
||||||
async fn verify_id_token(id_token: &str) -> SocialResult<Value> {
|
async fn verify_id_token(id_token: &str) -> SocialResult<GoogleJwtProfile> {
|
||||||
// 获取并解析公钥
|
// 获取并解析公钥
|
||||||
let public_keys = fetch_and_parse_google_public_keys().await?;
|
let public_keys = fetch_and_parse_google_public_keys().await?;
|
||||||
|
|
||||||
@ -37,28 +122,36 @@ async fn verify_id_token(id_token: &str) -> SocialResult<Value> {
|
|||||||
|
|
||||||
// 检查是否找到了有效的kid
|
// 检查是否找到了有效的kid
|
||||||
if kid.is_none() {
|
if kid.is_none() {
|
||||||
return Err(Box::new(Error::from(ErrorKind::InvalidToken)));
|
return Err(Box::new(MessageError::from("校验Token失败,未找到有效的kid")));
|
||||||
}
|
}
|
||||||
let kid = kid.unwrap();
|
let kid = kid.unwrap();
|
||||||
|
|
||||||
// 根据kid找到正确的公钥
|
// 根据kid找到正确的公钥
|
||||||
let key = public_keys.get(&kid).ok_or_else(|| Box::new(Error::from(ErrorKind::InvalidToken)))?;
|
let key = public_keys.get(&kid).ok_or_else(|| Box::new(MessageError::from("校验Token失败,未找到正确的公钥")))?;
|
||||||
|
|
||||||
// 验证Token
|
// 验证Token
|
||||||
let mut validation: Validation = Validation::new(Algorithm::RS256);
|
let mut validation: Validation = Validation::new(Algorithm::RS256);
|
||||||
validation.set_issuer(&["https://accounts.google.com"]);// 设置预期的发行者
|
validation.set_issuer(&["https://accounts.google.com", "accounts.google.com"]);// 设置预期的发行者
|
||||||
|
|
||||||
let decoded = decode::<Value>(id_token, &DecodingKey::from_rsa_pem(key.as_bytes())?, &validation)?;
|
let decoded = decode::<Value>(id_token, &DecodingKey::from_rsa_pem(key.as_bytes())?, &validation)?;
|
||||||
|
|
||||||
let claims = decoded.claims;
|
let claims: Value = decoded.claims;
|
||||||
// 进一步校验iss字段
|
let google_jwt_profile = GoogleJwtProfile::from(claims);
|
||||||
if let Some(issuer) = claims.get("iss") {
|
|
||||||
if issuer.as_str().unwrap() != "https://accounts.google.com" {
|
|
||||||
return Err(Box::new(Error::from(ErrorKind::InvalidToken)));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Err(Box::new(Error::from(ErrorKind::InvalidToken)));
|
|
||||||
}
|
|
||||||
Ok(claims)
|
|
||||||
|
|
||||||
|
// 校验有效期
|
||||||
|
if google_jwt_profile.exp < Utc::now().timestamp() {
|
||||||
|
return Err(Box::new(MessageError::from("校验Token失败,token有效期无效")));
|
||||||
|
}
|
||||||
|
// 校验签发时间
|
||||||
|
// if google_jwt_profile.iat > Utc::now().timestamp() {
|
||||||
|
// return Err(Box::new(Error::from(ErrorKind::InvalidToken)));
|
||||||
|
// }
|
||||||
|
// if google_jwt_profile.aud != config::GOOGLE_CLIENT_ID {
|
||||||
|
// }
|
||||||
|
// 校验iss字段
|
||||||
|
if google_jwt_profile.iss != "accounts.google.com" && google_jwt_profile.iss != "https://accounts.google.com" {
|
||||||
|
return Err(Box::new(MessageError::from("校验Token失败,token签发人非法")));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(google_jwt_profile)
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
type SocialResult<T> = Result<T, Box<dyn std::error::Error>>;
|
||||||
|
|
||||||
pub mod google;
|
pub mod google;
|
||||||
pub mod facebook;
|
pub mod facebook;
|
||||||
|
Loading…
Reference in New Issue
Block a user