rust-auth
🎯Skillfrom huiali/rust-skills
Enables robust JWT, API Key, and distributed token authentication with secure password storage in Rust.
Part of
huiali/rust-skills(30 items)
Installation
npx skills add huiali/rust-skills --skill rust-authSkill Details
"JWT 认证、API Key 认证、分布式 Token 存储、密码安全存储"
Overview
# Rust Auth - 认证授权技能
> 本技能提供完整的认证授权解决方案,包括 JWT、API Key、分布式 Token 存储等模式。
核心概念
1. 认证架构设计
```
认证授权架构
├── 认证层 (Authentication)
│ ├── JWT 认证
│ ├── API Key 认证
│ └── 多因素认证
├── 授权层 (Authorization)
│ ├── 基于角色的访问控制 (RBAC)
│ ├── 基于权限的访问控制
│ └── 细粒度权限
└── 会话管理
├── Token 管理
├── Session 存储
└── 并发控制
```
2. 认证方式对比
| 认证方式 | 适用场景 | 安全性 | 实现复杂度 |
|---------|---------|--------|----------|
| JWT Token | 前后端分离、API 访问 | 高 | 中 |
| API Key | 服务间调用、自动化脚本 | 中 | 低 |
| 双因素 | 高安全要求场景 | 极高 | 高 |
---
核心模式
1. JWT Token 认证
```rust
//! JWT 认证模块
use jsonwebtoken::{decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation};
use serde::{Deserialize, Serialize};
use std::time::{Duration, SystemTime};
use thiserror::Error;
/// JWT 错误类型
#[derive(Error, Debug)]
pub enum JwtError {
#[error("Token 解析失败: {0}")]
ParseError(String),
#[error("Token 验证失败: {0}")]
ValidationError(String),
#[error("Token 已过期")]
Expired,
#[error("Token 签名无效")]
InvalidSignature,
}
/// Token 声明(通用结构)
#[derive(Debug, Serialize, Deserialize)]
pub struct Claims {
pub sub: String, // 主体标识
pub name: Option
pub roles: Vec
pub exp: u64, // 过期时间
pub iat: u64, // 签发时间
// 业务字段可通过扩展字段添加
}
/// JWT 服务
pub struct JwtService {
encoding_key: EncodingKey,
decoding_key: DecodingKey,
secret: String,
expiry: Duration,
algorithm: Algorithm,
}
impl JwtService {
pub fn new(secret: String, expiry_seconds: u64) -> Self {
Self {
encoding_key: EncodingKey::from_secret(secret.as_bytes()),
decoding_key: DecodingKey::from_secret(secret.as_bytes()),
secret,
expiry: Duration::from_secs(expiry_seconds),
algorithm: Algorithm::HS256,
}
}
/// 生成 Token
pub fn generate_token(&self, subject: &str, roles: &[String]) -> Result
let now = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
.as_secs();
let claims = Claims {
sub: subject.to_string(),
name: None,
roles: roles.to_vec(),
exp: now + self.expiry.as_secs(),
iat: now,
};
encode(&Header::new(self.algorithm), &claims, &self.encoding_key)
.map_err(|e| JwtError::ParseError(e.to_string()))
}
/// 验证并解析 Token
pub fn verify_token(&self, token: &str) -> Result
let validation = Validation::new(self.algorithm);
decode::
.map(|data| data.claims)
.map_err(|e| match e.kind() {
jsonwebtoken::errors::ErrorKind::ExpiredSignature => JwtError::Expired,
jsonwebtoken::errors::ErrorKind::InvalidSignature => JwtError::InvalidSignature,
_ => JwtError::ValidationError(e.to_string()),
})
}
/// 检查 Token 是否即将过期(剩余 < 10 分钟返回 true)
pub fn is_expiring_soon(&self, claims: &Claims) -> bool {
let now = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
.as_secs();
claims.exp - now < 600
}
}
```
2. API Key 认证
```rust
//! API Key 认证模块
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
/// API Key 配置
#[derive(Debug, Clone)]
pub struct ApiKeyConfig {
pub prefix: String, // Key 前缀
pub secret: String, // 签名密钥
pub expiry_days: i64, // 有效期
pub allowed_ips: Vec
}
/// API Key 信息
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ApiKeyInfo {
pub key_id: String,
pub owner_id: String,
pub secret_hash: String,
pub created_at: DateTime
pub expires_at: DateTime
pub last_used_at: Option
pub disabled: bool,
pub scopes: Vec
}
/// API Key 错误
#[derive(Debug)]
pub enum ApiKeyError {
InvalidKey,
KeyExpired,
KeyRevoked,
SignatureMismatch,
}
/// API Key 生成器
pub struct ApiKeyGenerator;
impl ApiKeyGenerator {
/// 生成 API Key
/// 格式: {prefix}_{key_id}_{signature}
pub fn generate(config: &ApiKeyConfig, owner_id: &str) -> (String, String) {
let key_id = Self::generate_key_id();
let secret = Self::generate_secret();
let signature = Self::compute_signature(config, &key_id, &secret);
let api_key = format!("{}_{}_{}", config.prefix, key_id, signature);
let secret_hash = Self::hash_secret(config, &secret);
(api_key, secret_hash)
}
/// 验证 API Key
pub async fn verify(
config: &ApiKeyConfig,
api_key: &str,
ip: Option<&str>,
key_info: &ApiKeyInfo,
) -> Result<(), ApiKeyError> {
let parts: Vec<&str> = api_key.split('_').collect();
if parts.len() != 3 {
return Err(ApiKeyError::InvalidKey);
}
if key_info.disabled {
return Err(ApiKeyError::KeyRevoked);
}
if key_info.expires_at < Utc::now() {
return Err(ApiKeyError::KeyExpired);
}
if let Some(client_ip) = ip {
if !config.allowed_ips.is_empty()
&& !config.allowed_ips.contains(&client_ip.to_string()) {
return Err(ApiKeyError::InvalidKey);
}
}
let expected_signature = Self::compute_signature(config, parts[1], "stored");
if parts[2] != expected_signature {
return Err(ApiKeyError::SignatureMismatch);
}
Ok(())
}
fn generate_key_id() -> String {
use rand::Rng;
const CHARSET: &[u8] = b"abcdefghijklmnopqrstuvwxyz0123456789";
let mut rng = rand::thread_rng();
(0..16).map(|_| CHARSET[rng.gen_range(0..CHARSET.len())] as char).collect()
}
fn generate_secret() -> String {
use rand::Rng;
const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*";
let mut rng = rand::thread_rng();
(0..32).map(|_| CHARSET[rng.gen_range(0..CHARSET.len())] as char).collect()
}
fn compute_signature(config: &ApiKeyConfig, key_id: &str, secret: &str) -> String {
let data = format!("{}{}", key_id, secret);
let mut hasher = Sha256::new();
hasher.update(data.as_bytes());
hasher.update(config.secret.as_bytes());
format!("{:x}", hasher.finalize())[..8].to_string()
}
fn hash_secret(config: &ApiKeyConfig, secret: &str) -> String {
let mut hasher = Sha256::new();
hasher.update(secret.as_bytes());
hasher.update(config.secret.as_bytes());
format!("{:x}", hasher.finalize())
}
}
```
3. 分布式 Token 存储
```rust
//! 分布式 Token 存储
use redis::AsyncCommands;
/// Token 存储配置
#[derive(Debug, Clone)]
pub struct TokenStoreConfig {
pub redis_url: String,
pub prefix: String,
}
pub struct TokenStore {
redis: Option
config: TokenStoreConfig,
}
impl TokenStore {
pub async fn new(config: TokenStoreConfig) -> Result
let redis = if config.redis_url.starts_with("redis://") {
let client = redis::Client::open(config.redis_url.as_str())?;
let conn = redis::aio::ConnectionManager::new(client).await?;
Some(conn)
} else {
None
};
Ok(Self { redis, config })
}
/// 存储 Token(带并发控制)
pub async fn store_token(
&self,
user_id: &str,
token_id: &str,
claims: &Claims,
max_concurrent: usize,
) -> Result<(), Box
if let Some(ref mut redis) = self.redis {
let prefix = &self.config.prefix;
let key = format!("{}:tokens:{}", prefix, user_id);
// 检查并发数量
let current_count: usize = redis.scard(&key).await?;
if current_count >= max_concurrent {
if let Some(old_token) = redis.spop::
redis.del(&format!("{}:token:{}", prefix, old_token)).await?;
}
}
let token_key = format!("{}:token:{}", prefix, token_id);
let token_data = serde_json::to_string(claims)?;
let ttl = claims.exp.saturating_sub(86400);
redis.set_ex(&token_key, token_data, ttl).await?;
redis.sadd(&key, token_id).await?;
}
Ok(())
}
/// 撤销 Token
pub async fn revoke_token(&self, user_id: &str, token_id: &str) -> Result<(), Box
if let Some(ref mut redis) = self.redis {
let prefix = &self.config.prefix;
redis.del(&format!("{}:token:{}", prefix, token_id)).await?;
redis.srem(&format!("{}:tokens:{}", prefix, user_id), token_id).await?;
}
Ok(())
}
/// 撤销用户所有 Token
pub async fn revoke_all_tokens(&self, user_id: &str) -> Result<(), Box
if let Some(ref mut redis) = self.redis {
let prefix = &self.config.prefix;
let set_key = format!("{}:tokens:{}", prefix, user_id);
let token_ids: Vec
for token_id in token_ids {
redis.del(&format!("{}:token:{}", prefix, token_id)).await?;
}
redis.del(&set_key).await?;
}
Ok(())
}
}
```
---
最佳实践
1. 认证中间件
```rust
//! 认证中间件
use actix_web::{
dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform},
Error, HttpMessage,
};
use futures::future::{ready, LocalBoxFuture, Ready};
use std::rc::Rc;
pub struct JwtAuthentication;
impl Transform for JwtAuthentication
where
S: Service
S::Future: 'static,
B: 'static,
{
type Response = ServiceResponse;
type Error = Error;
type InitError = ();
type Transform = JwtAuthMiddleware;
type Future = Ready
fn new_transform(&self, service: S) -> Self::Future {
ready(Ok(JwtAuthMiddleware { service: Rc::new(service) }))
}
}
pub struct JwtAuthMiddleware {
service: Rc,
}
impl Service
where
S: Service
S::Future: 'static,
B: 'static,
{
type Response = ServiceResponse;
type Error = Error;
type Future = LocalBoxFuture<'static, Result
forward_ready!(service);
fn call(&self, req: ServiceRequest) -> Self::Future {
let service = Rc::clone(&self.service);
Box::pin(async move {
let auth_header = req.headers()
.get("Authorization")
.and_then(|v| v.to_str().ok());
let token = match auth_header {
Some(header) if header.starts_with("Bearer ") => &header[7..],
_ => return Err(actix_web::error::ErrorUnauthorized("Invalid Authorization")),
};
let jwt_service = req.app_data::
.ok_or(actix_web::error::ErrorInternalServerError("JWT not configured"))?;
let claims = jwt_service.verify_token(token)
.map_err(|_| actix_web::error::ErrorUnauthorized("Invalid token"))?;
req.extensions_mut().insert(claims);
service.call(req).await
})
}
}
```
2. 密码安全存储
```rust
//! 密码安全存储
use argon2::{self, Config};
use rand::Rng;
/// 密码哈希服务
pub struct PasswordHasher;
impl PasswordHasher {
/// 哈希密码
pub fn hash_password(password: &str) -> Result
let salt = rand::thread_rng().gen::<[u8; 32]>();
let config = Config::default();
argon2::hash_raw(password.as_bytes(), &salt, &config)
.map(|bytes| {
let salt_b64 = base64::Engine::encode(&base64::engine::general_purpose::STANDARD, &salt);
let hash_b64 = base64::Engine::encode(&base64::engine::general_purpose::STANDARD, &bytes);
format!("$argon2id$v=19,m=4096,t=3,p=1${}${}$", salt_b64, hash_b64)
})
}
/// 验证密码
pub fn verify_password(password: &str, stored_hash: &str) -> Result
use subtle::ConstantTimeEq;
let parts: Vec<&str> = stored_hash.split('$').collect();
if parts.len() != 5 { return Ok(false); }
let salt = base64::Engine::decode(&base64::engine::general_purpose::STANDARD, parts[2]).unwrap_or_default();
let stored = base64::Engine::decode(&base64::engine::general_purpose::STANDARD, parts[3]).unwrap_or_default();
let config = Config::default();
let computed = argon2::hash_raw(password.as_bytes(), &salt, &config)?;
Ok(computed.as_slice().ct_eq(&stored))
}
}
```
---
常见问题排查
| 问题 | 原因 | 解决方案 |
|-----|------|---------|
| Token 过期 | 时间窗口问题 | 刷新 token |
| 并发登录异常 | Redis 连接 | 检查连接池 |
| API Key 盗用 | 未配置 IP 白名单 | 启用 IP 限制 |
| 密码验证慢 | Argon2 耗时 | 调整工作因子 |
---
关联技能
rust-web- Web 开发框架集成rust-middleware- 中间件设计rust-error- 错误处理rust-cache- Token 存储
More from this repository10
Provides expert Rust programming assistance, solving compilation errors, ownership, lifetimes, concurrency, and performance optimization challenges.
Indexes and provides quick navigation for 35 Rust skills across core, advanced, and expert categories.
Expertly handles Rust error scenarios by providing comprehensive guidance on Result, Option, error types, propagation, and panic strategies.
Handles advanced Rust async patterns like Stream processing, backpressure, select, cancellation, and concurrency management with Tokio.
Expertly handles Rust concurrency challenges by safely managing threads, async operations, and preventing race conditions and deadlocks.
Identifies and helps refactor Rust anti-patterns like unnecessary cloning, unwrapping, and inefficient iterations to improve code quality.
Builds robust Rust web services using frameworks like axum and actix, handling routing, database interactions, and API design with type-safe, performant code.
Skill
Analyzes and optimizes Rust generic abstractions and dispatch strategies for zero-cost performance and compile-time flexibility.
Expertly manages Rust mutability challenges, resolving borrowing conflicts and providing safe interior mutability strategies across different contexts.