Actix-web 中的权限中间件实现

发布于:2025-06-13 ⋅ 阅读:(15) ⋅ 点赞:(0)

Actix-web 中的权限中间件实现

介绍

在构建 Web 应用时,权限管理是一个关键功能。本文将介绍如何在 Actix-web 中实现一个权限中间件,用于保护路由并确保只有授权用户才能访问特定资源。

权限中间件实现

1. 定义中间件结构体

use actix_web::http::header::HeaderMap;
use actix_web::{dev::Service, dev::ServiceRequest, dev::ServiceResponse, Error};
use futures::future::{ready, Ready};
use std::pin::Pin;

pub struct Auth;

pub struct AuthMiddleware<S> {
    service: S,
}

2. 实现 Transform Trait

use actix_web::dev::Transform;

impl<S, B> Transform<S, ServiceRequest> for Auth
where
    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
    S::Future: 'static,
    B: 'static,
{
    type Response = ServiceResponse<B>;
    type Error = Error;
    type InitError = ();
    type Transform = AuthMiddleware<S>;
    type Future = Ready<Result<Self::Transform, Self::InitError>>;

    fn new_transform(&self, service: S) -> Self::Future {
        ready(Ok(AuthMiddleware { service }))
    }
}

3. 实现 Service Trait

use actix_web::web::Data;
use futures::Future;

impl<S, B> Service<ServiceRequest> for AuthMiddleware<S>
where
    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
    S::Future: 'static,
    B: 'static,
{
    type Response = ServiceResponse<B>;
    type Error = Error;
    type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;

    forward_ready!(service);

    fn call(&self, req: ServiceRequest) -> Self::Future {
        let path = req.path().to_string();
        let headers = req.headers().clone();
        let fut = self.service.call(req);

        Box::pin(async move {
            // 定义公共路径(无需授权)
            let public_paths = vec![
                "/api/auth/login",
                "/api/auth/register",
                "/api/posts",
                "/api/comments",
                "/api/auth/permissions",
                "/sse/stream",
                "/api/sse/stream",
            ];

            if public_paths.contains(&path.as_str()) {
                let res = fut.await?;
                Ok(res)
            } else {
                // 从请求头中提取令牌
                let token = extract_token(&headers).await;
                if let Some(token) = token {
                    // 验证令牌
                    let permission_result = has_permission(&token);
                    match permission_result {
                        Ok(_token_data) => {
                            info!("令牌有效");
                            let res = fut.await?;
                            Ok(res)
                        }
                        Err(err) => {
                            error!("解码令牌时发生错误: {:?}", err);
                            let err = AppError::Unauthorized("无效的令牌".to_string());
                            Err(err.into())
                        }
                    }
                } else {
                    let err = AppError::Unauthorized("令牌未找到".to_string());
                    Err(err.into())
                }
            }
        })
    }
}

4. 提取令牌工具函数

use actix_web::http::header::HeaderMap;

async fn extract_token(headers: &HeaderMap) -> Option<String> {
    if let Some(authorization_header) = headers.get("Authorization") {
        if let Ok(authorization_str) = authorization_header.to_str() {
            // 假设令牌格式为 "Bearer <token>"
            if let Some(token) = authorization_str.strip_prefix("Bearer ") {
                return Some(token.to_string());
            }
        }
    }
    None
}

5. 配置 Actix-web 应用

use actix_web::{web, App, HttpServer};
use actix_cors::Cors;

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    init_logger();
    let db_pool = create_db_pool().await.unwrap();

    let app_data = web::Data::new(db_pool);
    let notifier = web::Data::new(SseNotifier::new());

    let host = "127.0.0.1";
    let port = 8080;
    let server_addr = format!("{}:{}", host, port);
    log::info!("当前服务成功启动,监听地址为 http://{}", server_addr);

    HttpServer::new(move || {
        let cors = Cors::default()
            .allowed_origin("http://127.0.0.1:5502")
            .allowed_methods(vec!["GET", "POST", "PUT", "DELETE", "OPTIONS"])
            .allowed_headers(vec!["Content-Type", "Authorization", "ACCEPT"])
            .supports_credentials()
            .max_age(3600);

        App::new()
            .wrap(actix_web::middleware::Logger::default())
            .app_data(notifier.clone())
            .app_data(app_data.clone())
            .wrap(Auth)
            .wrap(cors)
            .configure(config_routes)
    })
    .bind(&server_addr)?
    .run()
    .await
}

代码说明

  • 中间件定义:定义了一个 Auth 结构体,并实现了 Transform Trait,使其实现中间件功能。
  • 验证逻辑:在中间件的 call 方法中,检查请求路径是否在公共路径列表中。如果不在公共路径列表中,则从请求头中提取令牌并进行验证。
  • 集成到应用:在 Actix-web 应用中,通过 .wrap(Auth) 将权限中间件集成到应用的中间件链中。

结论

通过实现权限中间件,可以有效地保护 Actix-web 应用中的路由,确保只有授权用户才能访问特定资源。这种中间件机制可以灵活地应用于各种需要权限控制的场景。