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 应用中的路由,确保只有授权用户才能访问特定资源。这种中间件机制可以灵活地应用于各种需要权限控制的场景。