Actix-web 中的权限中间件实现
介绍
在构建 Web 应用时,权限管理是一个关键功能。本文将介绍如何在 Actix-web 中实现一个权限中间件,用于保护路由并确保只有授权用户才能访问特定资源。
权限中间件实现
1. 定义中间件结构体
rustuse 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
rustuse 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
rustuse 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. 提取令牌工具函数
rustuse 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 应用
rustuse 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结构体,并实现了TransformTrait,使其实现中间件功能。 - 验证逻辑:在中间件的
call方法中,检查请求路径是否在公共路径列表中。如果不在公共路径列表中,则从请求头中提取令牌并进行验证。 - 集成到应用:在 Actix-web 应用中,通过
.wrap(Auth)将权限中间件集成到应用的中间件链中。
结论
通过实现权限中间件,可以有效地保护 Actix-web 应用中的路由,确保只有授权用户才能访问特定资源。这种中间件机制可以灵活地应用于各种需要权限控制的场景。
