如何更改actix-web中间件中的响应类型?

问题描述 投票:0回答:1

我是 Rust 新手,我正在尝试制作一个中间件来验证 JWT

我想要

Err(actix_web::error::ErrorUnauthorized("Invalid token"))
应该是带有 json 正文的正确 http 响应 我不知道在哪里更改所需的函数签名。

另外,有没有更简单的方法在 Rust 中实现 jwt 验证?我应该使用中间件来执行此操作,还是可以创建一个 util 函数并在控制器上验证相同的函数?哪种方式更优选、更高效?

use crate::models::user_models::Claims;
use actix_web::{
    dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform},
    Error,
};
use dotenv::{dotenv, var};
use jsonwebtoken::{self, decode, errors::ErrorKind, Algorithm, DecodingKey, Validation};
use log::{error, info};
use std::{
    future::{ready, Future, Ready},
    pin::Pin,
};

pub struct JWT;

impl<S, B> Transform<S, ServiceRequest> for JWT
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 = JWTMiddleware<S>;
    type Future = Ready<Result<Self::Transform, Self::InitError>>;

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

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

type LocalBoxFuture<T> = Pin<Box<dyn Future<Output = T> + 'static>>;

impl<S, B> Service<ServiceRequest> for JWTMiddleware<S>
where
    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
    S::Future: 'static,
    B: 'static,
{
    type Response = ServiceResponse<B>;
    type Error = Error;
    type Future = LocalBoxFuture<Result<Self::Response, Self::Error>>;

    forward_ready!(service);

    fn call(&self, req: ServiceRequest) -> Self::Future {
        info!("JWT verification started...");
        let header = req.headers().clone();
        let auth_header = header.get("Authorization");
        let token = match auth_header {
            Some(header_value) => {
                let value_str = header_value.to_str().unwrap_or("");
                if value_str.starts_with("Bearer ") {
                    value_str.trim_start_matches("Bearer ")
                } else {
                    ""
                }
            }
            None => "",
        };

        dotenv().ok();
        let secret_key = var("SECRET_KEY").unwrap();
        let decoding_key = DecodingKey::from_secret(secret_key.as_ref());

        let fut = self.service.call(req);

        match decode::<Claims>(&token, &decoding_key, &Validation::new(Algorithm::HS256)) {
            Ok(_) => {
                Box::pin(async move {
                    let res = fut.await?;
                    Ok(res)
                })
            }
            Err(err) => {
                match err.kind() {
                    ErrorKind::InvalidToken => Box::pin(async move {
                        Err(actix_web::error::ErrorUnauthorized("Invalid token"))
                    }),
                    ErrorKind::ExpiredSignature => Box::pin(async move {
                        Err(actix_web::error::ErrorUnauthorized("Expired token"))
                    }),
                    _ => {
                        Box::pin(
                            async move { Err(actix_web::error::ErrorUnauthorized("Unauthorized")) },
                        )
                    }
                }
            }
        }
    }
}

货物.toml

[dependencies]
r2d2_sqlite = "0.24.0"
actix-web = "4.5.1"
diesel = { version = "2.1.5", features = [
    "sqlite",
    "r2d2",
    "returning_clauses_for_sqlite_3_35",
] }
diesel_migrations = "2.1.0"
dotenv = "0.15.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0.114"
env_logger = "0.11.3"
jsonwebtoken = "9.3.0"
bcrypt = "0.15.1"
rand = "0.8.5"
chrono = "0.4.38"
regex = "1.10.4"
futures = "0.3.30"
futures-util = "0.3.30"
log = "0.4.21"
rust jwt middleware httpresponse actix-web
1个回答
0
投票

我建议使用

FromRequest
,而不是中间件。这允许您根据端点注入的参数指定创建经过身份验证和未经身份验证的端点。 (由于您没有提供 SSCCE ,因此对原始生产/工作代码进行了大量修改,未经过测试)

例如


impl FromRequest for User
{
    type Error = Error;
    type Future = Pin<Box<dyn Future<Output = Result<User, Error>>>>;

    fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
        let auth_hdr = req.headers().get(AUTHORIZATION).cloned();
        let pool = req.app_data::<Data<DbPool>>().unwrap().get_ref().clone();
        Box::pin(async move {
            match user_from_req(&kc_srvc, auth_hdr, pool).await? {
                Some(user) => Ok(User),
                None => Err(Error::NotAuthenticated("not authenticated".into())),
            }
        })
    }
}

pub enum OptionalRequester {
    Anonymous,
    Authenticated(User),
}

impl FromRequest for OptionalRequester
{
    type Error = Error;
    type Future = Pin<Box<dyn Future<Output = Result<OptionalRequester, Error>>>>;

    fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
        let auth_hdr = req.headers().get(AUTHORIZATION).cloned();
        let pool = req.app_data::<Data<DbPool>>().unwrap().get_ref().clone();
        Box::pin(async move {
            match user_from_req(&kc_srvc, auth_hdr, pool).await? {
                Some(user) => Ok(OptionalRequester::Authenticated(user)),
                None => Ok(OptionalRequester::Anonymous),
            }
        })
    }
}

其用法如下:

// MUST be authenticated
pub async fn get_me(requester: User) -> String {
    Ok(requester.name)
}

// MAY be authenticated
pub async fn get_authenticated(optional_user: OptionalRequester) -> String {
    Ok(match optional_user {
        OptionalRequester::Anonymous => "anonymous".into(),
        OptionalRequester::Authenticated(_) => "authenticated".into(),
    })
}

// authentication IGNORED
pub async fn get_dont_care_about_auth() -> String {
    Ok("maybe you're authenticated, maybe you're not".into())
}
© www.soinside.com 2019 - 2024. All rights reserved.