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;
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct JWTToken {
pub id: Option<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>,
pub scope: Option<String>,
#[serde(rename = "departmentName")]
pub department_name: Option<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>,
}
#[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 {
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!")), }
}
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")), ErrorKind::InvalidIssuer => Err(Error::from("InvalidIssuer")), _ => Err(Error::from("InvalidToken other errors")),
},
}
}
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 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")), ErrorKind::InvalidIssuer => Err(Error::from("InvalidIssuer")), _ => Err(Error::from("InvalidToken other errors")),
},
}
}
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")),
},
}
}
}
#[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 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"))
}
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, };
encode(
&Header::default(),
&claims,
&EncodingKey::from_secret(jwt_secret.as_bytes()),
)
.map_err(|e| Error::from(format!("Failed to generate local service token: {}", e)))
}