tina-core 0.0.2

Tina platform
Documentation
//! token验证处理
#![cfg(feature = "server-http")]
use crate::app_system_error;
use crate::tina::constant::Constants;
use crate::tina::core::domain::login_user::LoginUser;
use crate::tina::data::json::JsonToString;
use crate::tina::data::AppResult;
use crate::tina::server::http::request_ext::RequestExt;
use crate::tina::util::client::ClientUtil;
use crate::tina::util::not_empty::INotEmpty;
use crate::tina::util::string::AsStr;
use chrono::Local;
use indexmap::IndexMap;
use jsonwebtoken::{decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation};
use serde_json::{Number, Value};
use std::borrow::Cow;
use std::marker::PhantomData;
use std::time::Duration;
use tracing::warn;
use uuid::Uuid;

use super::cache::ICacheService;

/// Token服务
#[cfg(feature = "redis")]
pub type TokenService = ITokenService<crate::tina::redis::cache::RedisCache>;
/// Token服务
#[cfg(not(feature = "redis"))]
pub type TokenService = ITokenService<crate::tina::core::service::cache::NoCacheService>;

/// token验证处理
pub struct ITokenService<T: ICacheService> {
    _phantom: PhantomData<T>,
}

impl<T: ICacheService> Default for ITokenService<T> {
    fn default() -> Self {
        Self::new()
    }
}

impl<T: ICacheService> ITokenService<T> {
    #[allow(unused)]
    const MILLIS_SECOND: i64 = 1000;
    #[allow(unused)]
    const MILLIS_MINUTE: i64 = 60 * Self::MILLIS_SECOND;
    #[allow(unused)]
    const MILLIS_MINUTE_TEN: i64 = 20 * 60 * 1000;

    /// 构建
    pub fn new() -> Self {
        Self {
            _phantom: Default::default(),
        }
    }

    /// 获取用户身份信息
    #[instrument]
    pub async fn get_login_user<Req: RequestExt + ?Sized>(req: &Req) -> AppResult<Option<LoginUser>> {
        // 获取请求携带的令牌
        let token = Self::get_token(req)?;
        if token.not_empty() {
            return match get_login_user_by_token::<Req, T>(req, token.as_str()).await {
                Ok(user) => Ok(user),
                Err(err) => {
                    warn!("获取用户身份信息异常: {}", err);
                    Ok(None)
                }
            };
        }
        Ok(None)
    }
    /// 设置用户身份信息
    #[instrument]
    pub async fn set_login_user<Req: RequestExt + ?Sized>(req: &Req, login_user: &mut LoginUser) -> AppResult<()> {
        if login_user.token.not_empty() {
            Self::refresh_token(req, login_user).await?;
        }
        Ok(())
    }
    /// 删除用户身份信息
    #[instrument]
    pub async fn del_login_user<Req: RequestExt + ?Sized>(req: &Req, token: &str) -> AppResult<()> {
        if token.not_empty() {
            let user_key = get_token_key(token);
            let application = req.get_application()?;
            let service = application.get_cache_service::<T>()?;
            service.delete_object(&application, user_key.as_str()).await?;
        }
        Ok(())
    }
    /// 创建令牌
    #[instrument]
    pub async fn create_token<Req: RequestExt + ?Sized>(req: &Req, login_user: &mut LoginUser) -> AppResult<String> {
        let token = Uuid::new_v4().to_string();
        login_user.token = Some(token.clone());
        Self::set_user_agent(req, login_user)?;
        Self::refresh_token(req, login_user).await?;

        let mut claims = IndexMap::<String, Value>::new();
        claims.insert(Constants::LOGIN_USER_KEY.to_string(), Value::String(token));
        let application = req.get_application()?;
        let security_config = application.get_security_config()?;
        let secret = security_config.token_secret.as_str();
        let token_string = Self::create_token_by_claims(secret, &mut claims)?;
        Ok(token_string)
    }
    /// 验证令牌有效期,相差不足20分钟,自动刷新缓存
    #[instrument]
    pub async fn verify_token<Req: RequestExt + ?Sized>(req: &Req, login_user: &mut LoginUser) -> AppResult<()> {
        let expire_time = login_user.expire_time.unwrap_or_default();
        let current_time = Local::now().timestamp_millis();
        if expire_time - current_time <= Self::MILLIS_MINUTE_TEN {
            Self::refresh_token(req, login_user).await?;
        }
        Ok(())
    }
    /// 刷新令牌有效期
    #[instrument]
    pub async fn refresh_token<Req: RequestExt + ?Sized>(req: &Req, login_user: &mut LoginUser) -> AppResult<()> {
        let application = req.get_application()?;
        let expire_time = application.get_security_config()?.token_expire_time as i64;
        let expire_time = expire_time * Self::MILLIS_MINUTE;
        let cur_time_millis = Local::now().timestamp_millis();
        login_user.login_time = Some(cur_time_millis);
        login_user.expire_time = Some(cur_time_millis + expire_time);
        // 根据uuid将loginUser缓存
        let user_key = get_token_key(login_user.token.as_str());
        let service = application.get_cache_service::<T>()?;
        service.set_cache_object_with_timeout(&application, user_key.as_str(), login_user, Duration::from_millis(expire_time as u64)).await
    }
    /// 设置用户代理信息
    #[instrument]
    pub fn set_user_agent<Req: RequestExt + ?Sized>(req: &Req, login_user: &mut LoginUser) -> AppResult<()> {
        let ip = req.get_remote_ip_address();
        login_user.ipaddr = Some(ip.to_string());
        login_user.login_location = Some("XX XX".to_string());

        let user_agent = req.get_user_agent();
        let user_agent_str = user_agent.as_ref();
        if user_agent_str.not_empty() {
            login_user.os = Some(ClientUtil::get_user_agent_os(user_agent_str).into_owned());
            login_user.device = Some(ClientUtil::get_user_agent_browser(user_agent_str).into_owned());
        }
        Ok(())
    }
    /// 从数据声明生成令牌
    #[instrument]
    fn create_token_by_claims(secret: &str, claims: &mut IndexMap<String, Value>) -> AppResult<String> {
        if !claims.contains_key("exp") {
            claims.insert("exp".to_string(), Value::Number(Number::from(Local::now().timestamp_millis())));
        }
        let token = encode(&Header::new(Algorithm::HS512), &claims, &EncodingKey::from_secret(secret.as_bytes()))
            .map_err(|err| app_system_error!(format!("create token failed! reason: {}, claims: {:?}", err, claims)))?;
        Ok(token)
    }
    ///从令牌中获取数据声明
    #[instrument]
    fn parse_token(secret: &str, token: &str) -> AppResult<IndexMap<String, Value>> {
        let token =
            decode::<IndexMap<String, Value>>(token, &DecodingKey::from_secret(secret.as_bytes()), &Validation::new(Algorithm::HS512))
                .map_err(|err| app_system_error!(format!("parse token failed: {}, reason: {}", token, err)))?;
        Ok(token.claims)
    }
    /// 从令牌中获取用户名
    #[instrument]
    pub fn get_username_from_token(secret: &str, token: &str) -> AppResult<Option<String>> {
        let mut claims = Self::parse_token(secret, token)?;
        match claims.remove("sub") {
            None => Ok(None),
            Some(sub) => Ok(Some(sub.to_string_value())),
        }
    }
    /// 获取请求token
    #[instrument]
    fn get_token<Req: RequestExt + ?Sized>(req: &Req) -> AppResult<Option<String>> {
        let token = get_token_value(req)?;
        let token = token.as_str();
        match token.not_empty() && token.starts_with(Constants::TOKEN_PREFIX) {
            true => {
                let token = token.replace(Constants::TOKEN_PREFIX, "");
                Ok(Some(token))
            }
            false => Ok(None),
        }
    }
}

#[instrument]
async fn get_login_user_by_token<Req: RequestExt + ?Sized, T: ICacheService>(req: &Req, token: &str) -> AppResult<Option<LoginUser>> {
    let application = req.get_application()?;
    let security_config = application.get_security_config()?;
    let secret = security_config.token_secret.as_str();
    let mut claims = TokenService::parse_token(secret, token)?;
    // 解析对应的权限以及用户信息
    let uuid = claims.remove(Constants::LOGIN_USER_KEY).map(|v| v.to_string_value());
    match uuid {
        None => Ok(None),
        Some(uuid) => {
            let user_key = get_token_key(uuid.as_str());
            let service = application.get_cache_service::<T>()?;
            let user = service.get_cache_object(&application, user_key.as_str()).await?;
            Ok(user)
        }
    }
}

#[instrument]
fn get_token_value<Req: RequestExt + ?Sized>(req: &Req) -> AppResult<Option<Cow<str>>> {
    let application = req.get_application()?;
    let security_config = application.get_security_config()?;
    let header_name = security_config.token_header_name.as_str();
    req.get_request_header(header_name)
}

#[instrument]
fn get_token_key(uuid: &str) -> String {
    format!("{}{}", Constants::LOGIN_TOKEN_KEY, uuid)
}

#[allow(unused)]
#[cfg(test)]
#[cfg(feature = "redis")]
mod test {
    use crate::tina::constant::Constants;
    use crate::tina::core::service::token::TokenService;
    use crate::tina::redis::cache::RedisCache;
    use indexmap::IndexMap;
    use serde_json::Value;
    use std::error::Error;

    #[test]
    #[ignore]
    fn test_parse() -> Result<(), Box<dyn Error>> {
        let token = "eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6IjU4ZmNjZTU3LWMyYzktNDgyMi1iNDQ0LTRmMTc0N2U4YWM5MyJ9.sNfAQ3ur8cb4I695P3OEfvl9voYGGKJicP1VeSrwr5t-YkP7smviPTHvpsg17Tghs7iHgTfmOmaykpuOaVCrnw";
        let header = jsonwebtoken::decode_header(token)?;
        println!("{:#?}", header);
        let map = TokenService::parse_token("abcdefghijklmnopqrstuvwxyz", token)?;
        println!("{:#?}", map);
        Ok(())
    }

    #[test]
    #[ignore]
    fn create_token() -> Result<(), Box<dyn Error>> {
        let token = "58fcce57-c2c9-4822-b444-4f1747e8ac93";
        let mut claims = IndexMap::<String, Value>::new();
        claims.insert(Constants::LOGIN_TOKEN_KEY.to_string(), Value::String(token.to_string()));
        let tokens = TokenService::create_token_by_claims("abcdefghijklmnopqrstuvwxyz", &mut claims)?;
        println!("{}", tokens);
        let map = TokenService::parse_token("abcdefghijklmnopqrstuvwxyz", tokens.as_str())?;
        println!("{:#?}", map);
        Ok(())
    }
}