genies_core 1.8.0

整合DDD与Dapr 的 core 库
Documentation
/*
 * @Author: tzw
 * @Date: 2021-07-21 22:45:16
 * @LastEditors: tzw
 * @LastEditTime: 2022-01-09 21:12:57
 */
use crate::error::Error;
use jsonwebtoken::errors::ErrorKind;
use jsonwebtoken::{decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation};
use log::*;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::str::FromStr;


/// JWT 鉴权 Token结构
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct JWTToken {
    //账号id
    pub id:  Option<String>,
    //账号
    // pub account: String,
    // //权限集合
    // pub permissions: Vec<String>,
    // //角色id集合
    // pub role_ids: Vec<String>,
    //过期时间
    pub exp: Option<usize>,
    pub iat: Option<usize>,
    pub jti: Option<String>,
    pub iss: Option<String>,
    pub sub: Option<String>,
    pub typ: Option<String>,
    pub azp: Option<String>,
    pub session_state: Option<String>,
    pub acr: Option<String>,
    pub realm_access: Option<Value>,
    pub resource_access: Option<Value>,
    // // "realm_access": {
    // // 	"roles": ["offline_access", "nurse", "uma_authorization", "user"]
    // // },
    // // "resource_access": {
    // // 	"tdnis": {
    // // 		"roles": ["nurseManager", "user"]
    // // 	}
    // // },
    pub scope: Option<String>,
    #[serde(rename = "departmentName")]
    pub department_name: Option<String>,
    // pub address: String,
    #[serde(rename = "departmentCode")]
    pub department_code: Option<String>,
    #[serde(rename = "departmentId")]
    pub department_id: Option<String>,
    pub roles: Option<Vec<String>>,
    pub groups: Option<Vec<String>>,
    pub dept: Option<Vec<String>>,
    pub preferred_username: Option<String>,
    pub given_name: Option<String>,
    #[serde(rename = "userId")]
    pub user_id: Option<String>,
    pub name: Option<String>,
    #[serde(rename = "departmentAbstract")]
    pub department_abstract: Option<String>,
}

/// auth-admin 签发的本地 JWT Claims 格式
#[derive(Debug, Serialize, Deserialize)]
struct LocalTokenClaims {
    pub sub: String,
    pub uid: Option<i64>,
    pub name: Option<String>,
    pub iat: usize,
    pub exp: usize,
}

impl JWTToken {
    /// create token
    /// secret: your secret string
    pub fn create_token(&self, secret: &str) -> Result<String,  crate::error::Error> {
        match encode(
            &Header::default(),
            self,
            &EncodingKey::from_secret(secret.as_ref()),
        ) {
            Ok(t) => Ok(t),
            Err(_) => Err(Error::from("JWTToken encode fail!")), // in practice you would return the error
        }
    }
    /// verify token invalid
    /// secret: your secret string
    pub fn verify(secret: &str, token: &str) -> Result<JWTToken,  crate::error::Error> {
        let validation = Validation {
            ..Validation::default()
        };
        match decode::<JWTToken>(
            token,
            &DecodingKey::from_secret(secret.as_ref()),
            &validation,
        ) {
            Ok(c) => Ok(c.claims),
            Err(err) => match *err.kind() {
                ErrorKind::InvalidToken => Err(Error::from("InvalidToken")), // Example on how to handle a specific error
                ErrorKind::InvalidIssuer => Err(Error::from("InvalidIssuer")), // Example on how to handle a specific error
                _ => Err(Error::from("InvalidToken other errors")),
            },
        }
    }
 // 从keycloak 服务中读取相关的加密算法,和相关的key 然后进行解密验证
    pub fn verify_with_keycloak(keycloak: &Keys, token: &str) -> Result<JWTToken,  crate::error::Error> {
        let n = keycloak.keys[0].n.as_ref().unwrap();
        let e = keycloak.keys[0].e.as_ref().unwrap();
        // let kty = keycloak.keys[0].kty.as_ref().unwrap();
        let alg = keycloak.keys[0].alg.as_ref().unwrap();

        let algorithm = Algorithm::from_str(alg).unwrap();

        let validation = Validation::new(algorithm);

        let jwt_token =
            decode::<JWTToken>(token, &DecodingKey::from_rsa_components(n, e), &validation);

        match jwt_token {
            Ok(c) => Ok(c.claims),
            Err(err) => match *err.kind() {
                ErrorKind::InvalidToken => Err(Error::from("InvalidToken")), // Example on how to handle a specific error
                ErrorKind::InvalidIssuer => Err(Error::from("InvalidIssuer")), // Example on how to handle a specific error
                _ => Err(Error::from("InvalidToken other errors")),
            },
        }
    }

    /// 使用本地密钥验证 auth-admin 签发的 JWT Token
    /// 解码 LocalTokenClaims 格式并映射为 JWTToken
    pub fn verify_local(secret: &str, token: &str) -> Result<JWTToken, crate::error::Error> {
        let validation = Validation {
            ..Validation::default()
        };
        match decode::<LocalTokenClaims>(
            token,
            &DecodingKey::from_secret(secret.as_ref()),
            &validation,
        ) {
            Ok(c) => {
                let claims = c.claims;
                Ok(JWTToken {
                    preferred_username: Some(claims.sub.clone()),
                    sub: Some(claims.sub),
                    user_id: claims.uid.map(|id| id.to_string()),
                    name: claims.name,
                    exp: Some(claims.exp),
                    iat: Some(claims.iat),
                    ..Default::default()
                })
            }
            Err(err) => match *err.kind() {
                ErrorKind::InvalidToken => Err(Error::from("InvalidToken")),
                ErrorKind::InvalidIssuer => Err(Error::from("InvalidIssuer")),
                _ => Err(Error::from("InvalidToken other errors")),
            },
        }
    }
}

///keycloak 秘钥
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Keys {
    pub keys: Vec<Key>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Key {
    pub kid: Option<String>,
    pub kty: Option<String>,
    pub alg: Option<String>,
    #[serde(rename = "use")]
    pub r#use: Option<String>,
    pub n: Option<String>,
    pub e: Option<String>,
    pub x5c: Option<Vec<String>>,
    pub x5t: Option<String>,
    #[serde(rename = "x5t#S256")]
    pub x5t_s256: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct KeyCloakAccessToken {
    pub access_token: Option<String>,
    pub expires_in: Option<i64>,
    pub refresh_expires_in: Option<i64>,
    pub refresh_token: Option<String>,
    pub token_type: Option<String>,
    // pub not-before-polic:Option<String>,
    pub session_state: Option<String>,
    pub scope: Option<String>,
}

pub async fn get_keycloak_keys(keycloak_auth_server_url: &str, keycloak_realm: &str) -> Result<Keys, Error> {
    let c = format!(
        "{}realms/{}/protocol/openid-connect/certs",
        keycloak_auth_server_url, keycloak_realm
    );
    log::info!("开始访问keycloak:{}", c);

    let client = reqwest::Client::builder()
        .timeout(std::time::Duration::from_secs(10))
        .build()
        .map_err(|e| Error::from(format!("Failed to build HTTP client: {}", e)))?;

    let keys: Keys = client.get(&c)
        .send()
        .await
        .map_err(|e| Error::from(format!("Failed to fetch keycloak keys: {}", e)))?
        .json()
        .await
        .map_err(|e| Error::from(format!("Failed to parse keycloak keys: {}", e)))?;

    log::info!("取到了keycloak certs:{:?}", &keys);

    Ok(keys)
}

pub async fn get_temp_access_token(
    keycloak_auth_server_url: &str,
    keycloak_realm: &str,
    keycloak_resource: &str,
    keycloak_credentials_secret: &str,
) -> Result<String, Error> {
    let keycloak_url = format!(
        "{}realms/{}/protocol/openid-connect/token",
        keycloak_auth_server_url, keycloak_realm
    );

    let body_str = format!(
        "client_id={}&client_secret={}&grant_type=client_credentials",
        keycloak_resource, keycloak_credentials_secret
    );
    debug!(
        "开始取临时access_token,参数如下:{},{}",
        &keycloak_url, &body_str
    );

    let client = reqwest::Client::builder()
        .timeout(std::time::Duration::from_secs(10))
        .build()
        .map_err(|e| Error::from(format!("Failed to build HTTP client: {}", e)))?;

    let access_token: KeyCloakAccessToken = client
        .post(&keycloak_url)
        .header("Content-Type", "application/x-www-form-urlencoded")
        .body(body_str)
        .send()
        .await
        .map_err(|e| Error::from(format!("Failed to request access token: {}", e)))?
        .json()
        .await
        .map_err(|e| Error::from(format!("Failed to parse access token: {}", e)))?;

    debug!("取到了临时access_token:{:?}", &access_token);

    access_token
        .access_token
        .ok_or_else(|| Error::from("Access token is missing in response"))
}

/// 使用 jwt_secret 本地生成服务 JWT(auth_mode="local" 时使用)
/// 供 #[remote] 宏在 local 模式下获取/刷新 service account token
pub async fn get_local_service_token(jwt_secret: &str) -> Result<String, Error> {
    let now = std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .unwrap()
        .as_secs() as usize;

    let claims = LocalTokenClaims {
        sub: "service-account".to_string(),
        uid: None,
        name: Some("remote-service".to_string()),
        iat: now,
        exp: now + 3600, // 1小时有效期
    };

    encode(
        &Header::default(),
        &claims,
        &EncodingKey::from_secret(jwt_secret.as_bytes()),
    )
    .map_err(|e| Error::from(format!("Failed to generate local service token: {}", e)))
}