Rust Web身份验证库axum-login的使用:为Axum框架提供安全便捷的登录和会话管理功能

Rust Web身份验证库axum-login的使用:为Axum框架提供安全便捷的登录和会话管理功能

axum-login是一个为Axum框架提供用户识别、认证和授权的tower中间件库。

主要特性

  • 用户识别、认证和授权:通过AuthSession轻松管理认证和授权,它也是一个提取器,可以直接在Axum处理器中使用
  • 支持任意用户和后端:应用实现AuthUserAuthnBackend特性,支持任何用户类型和用户管理后端
  • 用户和组权限:通过AuthzBackend特性支持授权,允许应用定义自定义权限
  • 便捷的路由保护:通过login_requiredpermission_required宏提供中间件保护路由访问
  • 可靠的会话管理:使用tower-sessions进行高性能且符合人体工程学的会话管理

安装

在Cargo.toml中添加依赖:

[dependencies]
axum-login = "0.18.0"

使用示例

以下是使用axum-login的完整示例demo:

use axum::{
    routing::get,
    Router,
    response::IntoResponse,
    extract::FromRef,
};
use axum_login::{
    login_required,
    AuthManagerLayerBuilder,
    AuthUser,
    AuthnBackend,
    UserId,
};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use tokio::sync::RwLock;

// 定义用户类型
#[derive(Debug, Clone, Serialize, Deserialize)]
struct User {
    id: i64,
    name: String,
    password: String,
}

impl AuthUser for User {
    type Id = i64;

    fn id(&self) -> Self::Id {
        self.id
    }

    fn session_auth_hash(&self) -> &[u8] {
        self.password.as_bytes()
    }
}

// 定义认证后端
#[derive(Debug, Clone)]
struct Backend {
    users: Arc<RwLock<Vec<User>>>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
struct Credentials {
    username: String,
    password: String,
}

#[derive(Debug, thiserror::Error)]
enum AuthError {
    #[error("invalid credentials")]
    InvalidCredentials,
}

impl AuthnBackend for Backend {
    type User = User;
    type Credentials = Credentials;
    type Error = AuthError;

    async fn authenticate(
        &self,
        creds: Self::Credentials,
    ) -> Result<Option<Self::User>, Self::Error> {
        let users = self.users.read().await;
        
        for user in users.iter() {
            if user.name == creds.username && user.password == creds.password {
                return Ok(Some(user.clone()));
            }
        }
        
        Ok(None)
    }

    async fn get_user(
        &self,
        user_id: &UserId<Self>,
    ) -> Result<Option<Self::User>, Self::Error> {
        let users = self.users.read().await;
        Ok(users.iter().find(|u| u.id == *user_id).cloned())
    }
}

// 定义应用状态
#[derive(Debug, Clone, FromRef)]
struct AppState {
    backend: Backend,
}

#[tokio::main]
async fn main() {
    // 初始化用户数据库
    let users = Arc::new(RwLock::new(vec![
        User {
            id: 1,
            name: "alice".to_string(),
            password: "password123".to_string(),
        },
    ]));
    
    let backend = Backend { users };
    
    // 构建认证管理中间件
    let auth_layer = AuthManagerLayerBuilder::new(backend.clone(), || async move {
        // 会话过期时间配置
        Some(3600)
    }).build();
    
    let app = Router::new()
        .route("/", get(handler))
        .route("/protected", get(protected_handler))
        .layer(auth_layer)
        .with_state(AppState { backend });
    
    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}

async fn handler() -> impl IntoResponse {
    "Hello, World!"
}

#[login_required]
async fn protected_handler() -> impl IntoResponse {
    "Protected content!"
}

安全保证

这个crate使用#![forbid(unsafe_code)]确保所有实现都是100%安全的Rust代码。

完整示例代码

以下是一个更完整的示例,包含登录/注销功能:

use axum::{
    extract::State,
    Form,
    response::{IntoResponse, Redirect},
    routing::{get, post},
    Router,
};
use axum_login::{
    auth_session::{AuthSession, LoginGuard},
    AuthManagerLayerBuilder,
    AuthUser,
    AuthnBackend,
    UserId,
};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use tokio::sync::RwLock;

// 1. 定义用户类型
#[derive(Debug, Clone, Serialize, Deserialize)]
struct User {
    id: i64,
    name: String,
    password: String,
    email: String,
}

impl AuthUser for User {
    type Id = i64;

    fn id(&self) -> Self::Id {
        self.id
    }

    fn session_auth_hash(&self) -> &[u8] {
        self.password.as_bytes()
    }
}

// 2. 定义认证后端
#[derive(Debug, Clone)]
struct Backend {
    users: Arc<RwLock<Vec<User>>>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Credentials {
    pub username: String,
    pub password: String,
}

#[derive(Debug, thiserror::Error)]
pub enum AuthError {
    #[error("invalid credentials")]
    InvalidCredentials,
    #[error("user not found")]
    UserNotFound,
}

#[axum::async_trait]
impl AuthnBackend for Backend {
    type User = User;
    type Credentials = Credentials;
    type Error = AuthError;

    async fn authenticate(
        &self,
        creds: Self::Credentials,
    ) -> Result<Option<Self::User>, Self::Error> {
        let users = self.users.read().await;
        
        let user = users
            .iter()
            .find(|user| user.name == creds.username && user.password == creds.password);
        
        Ok(user.cloned())
    }

    async fn get_user(
        &self,
        user_id: &UserId<Self>,
    ) -> Result<Option<Self::User>, Self::Error> {
        let users = self.users.read().await;
        let user = users.iter().find(|user| user.id == *user_id);
        Ok(user.cloned())
    }
}

// 3. 定义应用状态
#[derive(Clone, FromRef)]
struct AppState {
    backend: Backend,
}

// 4. 处理器函数
async fn login(
    mut auth_session: AuthSession<Backend>,
    State(state): State<AppState>,
    Form(creds): Form<Credentials>,
) -> impl IntoResponse {
    let user = match auth_session.authenticate(creds).await {
        Ok(Some(user)) => user,
        Ok(None) => return Err(AuthError::InvalidCredentials),
        Err(_) => return Err(AuthError::InvalidCredentials),
    };

    auth_session.login(&user).await.unwrap();
    
    Redirect::to("/dashboard").into_response()
}

async fn logout(mut auth_session: AuthSession<Backend>) -> impl IntoResponse {
    auth_session.logout().await;
    Redirect::to("/login").into_response()
}

async fn dashboard(LoginGuard(user): LoginGuard<User>) -> impl IntoResponse {
    format!(
        "欢迎回来,{}! 你的用户ID是: {}",
        user.name, user.id
    )
}

async fn login_page() -> impl IntoResponse {
    r#"
    <!doctype html>
    <html>
        <head>
            <title>登录</title>
        </head>
        <body>
            <h1>登录</h1>
            <form action="/login" method="post">
                <div>
                    <label>用户名:
                        <input type="text" name="username" placeholder="输入用户名">
                    </label>
                </div>
                <div>
                    <label>密码:
                        <input type="password" name="password" placeholder="输入密码">
                    </label>
                </div>
                <button type="submit">登录</button>
            </form>
        </body>
    </html>
    "#
}

// 5. 主函数
#[tokio::main]
async fn main() {
    // 初始化用户数据库
    let users = Arc::new(RwLock::new(vec![
        User {
            id: 1,
            name: "alice".to_string(),
            password: "password123".to_string(),
            email: "alice@example.com".to_string(),
        },
        User {
            id: 2,
            name: "bob".to_string(),
            password: "password456".to_string(),
            email: "bob@example.com".to_string(),
        },
    ]));

    let backend = Backend { users };
    
    // 构建认证管理中间件
    let auth_layer = AuthManagerLayerBuilder::new(backend.clone(), || async move {
        Some(3600) // 会话过期时间(秒)
    }).build();

    let app = Router::new()
        .route("/", get(|| async { Redirect::to("/login") }))
        .route("/login", get(login_page))
        .route("/login", post(login))
        .route("/logout", get(logout))
        .route("/dashboard", get(dashboard))
        .layer(auth_layer)
        .with_state(AppState { backend });

    println!("服务器运行在 http://localhost:3000");
    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}

安全保证

这个crate使用#![forbid(unsafe_code)]确保所有实现都是100%安全的Rust代码。


1 回复

axum-login:为Axum框架提供安全便捷的登录和会话管理功能

介绍

axum-login 是一个为 Axum Web 框架设计的身份验证库,它简化了用户登录、会话管理和权限控制等常见身份验证功能的实现。这个库建立在 tower-sessions 之上,提供了开箱即用的解决方案,同时保持了 Axum 框架的灵活性和性能特点。

主要特性:

  • 基于会话的身份验证
  • 用户角色和权限管理
  • 与 Axum 框架无缝集成
  • 类型安全的中间件
  • 可扩展的设计

安装

在 Cargo.toml 中添加依赖:

[dependencies]
axum-login = "0.7"
tokio = { version = "1.0", features = ["full"] }
axum = "0.7"
serde = { version = "1.0", features = ["derive"] }

基本使用方法

1. 定义用户模型

首先定义一个用户类型并实现必要的 trait:

use axum_login::{AuthUser, AuthnBackend, UserId};
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct User {
    id: i64,
    name: String,
    password: String,
    role: String,
}

impl AuthUser for User {
    type Id = i64;

    fn id(&self) -> Self::Id {
        self.id
    }

    fn session_auth_hash(&self) -> &[u8] {
        self.password.as_bytes() // 实际应用中应该使用密码哈希
    }
}

2. 实现认证后端

#[derive(Debug, Clone)]
pub struct Backend;

#[derive(Debug, Clone, Deserialize)]
pub struct Credentials {
    pub username: String,
    pub password: String,
}

#[axum::async_trait]
impl AuthnBackend for Backend {
    type User = User;
    type Credentials = Credentials;
    type Error = std::convert::Infallible;

    async fn authenticate(
        &self,
        creds: Self::Credentials,
    ) -> Result<Option<Self::User>, Self::Error> {
        // 这里应该是实际的用户验证逻辑
        if creds.username == "admin" && creds.password == "password" {
            Ok(Some(User {
                id: 1,
                name: creds.username,
                password: creds.password,
                role: "admin".to_string(),
            }))
        } else {
            Ok(None)
        }
    }

    async fn get_user(
        &self,
        user_id: &UserId<Self>,
    ) -> Result<Option<Self::User>, Self::Error> {
        // 这里应该是实际的用户查询逻辑
        Ok(Some(User {
            id: *user_id,
            name: "admin".to_string(),
            password: "password".to_string(),
            role: "admin".to_string(),
        }))
    }
}

3. 设置路由和中间件

use axum::{Router, routing::get, response::IntoResponse};
use axum_login::{login_required, AuthManagerLayerBuilder};
use tower_sessions::{MemoryStore, SessionManagerLayer};

async fn protected_handler(user: User) -> impl IntoResponse {
    format!("Welcome, {}!", user.name)
}

async fn login_handler() -> impl IntoResponse {
    "Please login"
}

#[tokio::main]
async fn main() {
    let session_store = MemoryStore::default();
    let session_layer = SessionManagerLayer::new(session_store);

    let backend = Backend;
    let auth_layer = AuthManagerLayerBuilder::new(backend, session_layer).build();

    let app = Router::new()
        .route("/protected", get(protected_handler))
        .route("/login", get(login_handler))
        .layer(auth_layer);

    axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
        .serve(app.into_make_service())
        .await
        .unwrap();
}

4. 添加登录/登出功能

use axum::{response::Redirect, Form};
use axum_login::axum_sessions::extractors::WritableSession;

async fn login(
    mut auth: axum_login::AuthSession极狐GitLab 16.9 发布,带来多项 DevOps 增强功能

极狐GitLab 16.9 版本已正式发布,带来了多项 DevOps 增强功能,包括:

1. CI/CD 改进
- 新增 "needs" 依赖关系可视化功能
- 支持在 merge request 流水线中使用 when:manual 作业

2. 安全增强
- 静态应用安全测试(SAST)支持更多语言
- 依赖项扫描支持自动修复漏洞

3. 项目管理
- 议题看板支持自定义工作流状态
- 新增时间跟踪报告功能

4. 平台改进
- 容器镜像仓库性能优化
- 支持使用外部 PostgreSQL 数据库

该版本还包含多项性能优化和 bug 修复,建议所有用户升级。企业用户可以通过极狐GitLab 的自动更新功能或手动下载安装包进行升级。

极狐GitLab 作为专为中国市场打造的 GitLab 发行版,16.9 版本在保持与国际版 GitLab 功能同步的同时,还针对国内用户需求进行了本地化优化,包括更快的访问速度和符合中国法规的数据存储选项。
回到顶部