use sz_orm_auth::jwt::{JwtClaims, JwtEncoder};
fn now_ts() -> i64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs() as i64
}
#[test]
fn attack_forged_signature_rejected() {
let encoder = JwtEncoder::new("server-secret-42");
let claims = JwtClaims::new("user-1", now_ts() + 3600).with_roles(vec!["admin".into()]);
let token = encoder.encode(&claims).unwrap();
let attacker = JwtEncoder::new("attacker-secret");
let forged_claims = JwtClaims::new("user-1", now_ts() + 3600).with_roles(vec!["root".into()]);
let forged = attacker.encode(&forged_claims).unwrap();
assert!(
encoder.decode(&forged).is_err(),
"使用错误 secret 签发的 token 必须被拒绝"
);
assert!(encoder.decode(&token).is_ok());
}
#[test]
fn attack_expired_token_rejected() {
let encoder = JwtEncoder::new("server-secret-42");
let expired = JwtClaims::new("user-1", now_ts() - 300);
let token = encoder.encode(&expired).unwrap();
assert!(
encoder.decode(&token).is_err(),
"过期 token 必须被拒绝(decode 应校验 exp)"
);
}
#[test]
fn attack_tampered_payload_rejected() {
let encoder = JwtEncoder::new("server-secret-42");
let claims = JwtClaims::new("user-1", now_ts() + 3600).with_roles(vec!["user".into()]);
let token = encoder.encode(&claims).unwrap();
let parts: Vec<&str> = token.split('.').collect();
assert_eq!(parts.len(), 3);
let tampered_claims = JwtClaims::new("user-1", now_ts() + 3600).with_roles(vec!["root".into()]);
let tampered_json = serde_json::to_string(&tampered_claims).unwrap();
let tampered_b64 = base64_url_encode(tampered_json.as_bytes());
let tampered = format!("{}.{}.{}", parts[0], tampered_b64, parts[2]);
assert!(
encoder.decode(&tampered).is_err(),
"篡改 payload 但保留原签名的 token 必须被拒绝(签名覆盖 payload)"
);
}
#[test]
fn attack_weak_secret_guessing_fails() {
let encoder = JwtEncoder::new("correct-horse-battery-staple-2026");
let claims = JwtClaims::new("user-1", now_ts() + 3600);
let token = encoder.encode(&claims).unwrap();
for weak in [
"secret", "password", "123456", "admin", "changeme", "sz-orm",
] {
let guess = JwtEncoder::new(weak);
assert!(
guess.decode(&token).is_err(),
"使用弱 secret 猜测解码必须失败: {weak}"
);
}
assert!(encoder.decode(&token).is_ok());
}
#[test]
fn attack_malformed_tokens_do_not_panic() {
let encoder = JwtEncoder::new("server-secret-42");
for malformed in [
"",
"a.b",
"a.b.c.d",
"!!!.???.###",
"not-a-jwt",
"eyJ.eyJ.sig",
] {
let result = encoder.decode(malformed);
assert!(
result.is_err(),
"畸形 token 应优雅失败而非 panic/接受: {malformed:?}"
);
}
}
fn base64_url_encode(data: &[u8]) -> String {
use base64::Engine;
base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(data)
}