You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Actix Web中间件类型不匹配:期望HttpResponse<B>,实际得到HttpResponse

解决Actix Web JWT中间件的HttpResponse类型不匹配问题

问题描述

我尝试用Actix Web实现JWT验证中间件,逻辑是从请求头提取Bearer Token验证,通过后将用户结构体注入请求,失败则返回错误。但运行时出现类型不匹配错误:

impl<S, B> Service<ServiceRequest> for JwtMiddlewareMiddleware<S>
    |         - this type parameter
...
125 |                                 Ok::<ServiceResponse<B>, Error>(ServiceResponse::new(
    |                                                                 -------------------- arguments to this function are incorrect
126 |                                     req.request().clone(),
127 |                                     new_response,
    |                                     ^^^^^^^^^^^^ expected `HttpResponse<B>`, found `HttpResponse`
    |
    = note: expected struct `HttpResponse<B>`
               found struct `HttpResponse<BoxBody>`
note: associated function defined here

原中间件代码:

use actix_service::Service;
use actix_web::error::ResponseError;
use actix_web::{
    dev::{ServiceRequest, ServiceResponse},
    Error, HttpMessage, Result,
};
use actix_web::{http, HttpResponse};
use futures::future::{ok, Ready};
use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation};
use serde::Deserialize;
use std::pin::Pin;
use std::{env, fmt};

// JWT Claims structure
#[derive(Debug, Deserialize)]
struct Claims {
    user_id: String,
    user_name: String,
    exp: usize,
    iat: usize,
    iss: String,
    aud: String,
    sub: String,
    typ: String,
    jti: String,
    role: String,
    permission: String,
}

#[derive(Debug)]
struct UnauthorizedError {
    message: String,
}

impl UnauthorizedError {
    fn new(message: &str) -> Self {
        UnauthorizedError {
            message: message.to_string(),
        }
    }
}

impl ResponseError for UnauthorizedError {
    fn status_code(&self) -> http::StatusCode {
        http::StatusCode::UNAUTHORIZED
    }
}

impl fmt::Display for UnauthorizedError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "Unauthorized: {}", self.message)
    }
}

pub struct JwtMiddleware;

impl<S, B> actix_service::Transform<S, ServiceRequest> for JwtMiddleware
where
    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + Clone,
{
    type Response = ServiceResponse<B>;
    type Error = Error;
    type Transform = JwtMiddlewareMiddleware<S>;
    type InitError = ();
    type Future = Ready<Result<Self::Transform, Self::InitError>>;

    fn new_transform(&self, service: S) -> Self::Future {
        ok(JwtMiddlewareMiddleware { service })
    }
}

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

impl<S, B> Service<ServiceRequest> for JwtMiddlewareMiddleware<S>
where
    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + Clone,
{
    type Response = ServiceResponse<B>;
    type Error = Error;
    type Future = Pin<Box<dyn futures::Future<Output = Result<Self::Response, Self::Error>>>>;

    fn poll_ready(
        &self,
        cx: &mut std::task::Context<'_>,
    ) -> std::task::Poll<Result<(), Self::Error>> {
        self.service.poll_ready(cx)
    }

    fn call(&self, req: ServiceRequest) -> Self::Future {
        // Extract the Authorization header
        let auth_header = req.headers().get("Authorization");

        if let Some(token) = auth_header {
            if let Ok(token_str) = token.to_str() {
                if token_str.starts_with("Bearer ") {
                    // Extract the JWT from the header
                    let jwt = token_str[7..].to_string();
                    let secret_key = env::var("SECRET_KEY").expect("SECRET_KEY not set");
                    let secret = secret_key.as_bytes(); // Replace with your secret key

                    // Decoding key and validation settings
                    let decoding_key = DecodingKey::from_secret(secret);
                    let validation = Validation {
                        algorithms: vec![Algorithm::HS256], // Adjust the algorithm accordingly
                        ..Default::default()
                    };

                    match decode::<Claims>(&jwt, &decoding_key, &validation) {
                        Ok(_) => {
                            // JWT is valid, continue to the inner service
                            let fut = self.service.call(req);
                            Box::pin(async move {
                                let res = fut.await?;
                                Ok::<ServiceResponse<B>, Error>(res)
                            }) as Self::Future
                        }
                        Err(_) => {
                            // JWT is invalid, return unauthorized response
                            let err = UnauthorizedError::new("Access denied");
                            let res = req.error_response(err);
                            Box::pin(async move {
                                let new_response = HttpResponse::Ok().finish(); // Create a new HttpResponse
                                Ok::<ServiceResponse<B>, Error>(ServiceResponse::new(
                                    req.request().clone(),
                                    new_response,
                                ))
                            }) as Self::Future
                        }
                    }
                } else {
                    // Token doesn't start with "Bearer "
                    let err = UnauthorizedError::new("Bad Request");
                    let res = req.error_response(err);
                    Box::pin(async {
                        let new_response = HttpResponse::Ok().finish(); // Create a new HttpResponse
                        Ok::<ServiceResponse<B>, Error>(ServiceResponse::new(
                            req.request().clone(),
                            new_response,
                        ))
                    }) as Self::Future
                }
            } else {
                // Token is not a valid UTF-8 string
                let err = UnauthorizedError::new("Bad Request");
                let res = req.error_response(err);
                Box::pin(async {
                    let new_response = HttpResponse::Ok().finish(); // Create a new HttpResponse
                    Ok::<ServiceResponse<B>, Error>(ServiceResponse::new(
                        req.request().clone(),
                        new_response,
                    ))
                }) as Self::Future
            }
        } else {
            // Token not found in the header
            let err = UnauthorizedError::new("Unauthorized");
            let res = req.error_response(err);
            Box::pin(async {
                let new_response = HttpResponse::Ok().finish(); // Create a new HttpResponse
                Ok::<ServiceResponse<B>, Error>(ServiceResponse::new(
                    req.request().clone(),
                    new_response,
                ))
            }) as Self::Future
        }
    }
}

解决方案

错误根源是手动创建的HttpResponse默认使用BoxBody类型,但中间件要求返回HttpResponse<B>。实际上ServiceRequest::error_response已经能直接生成符合类型要求的响应,无需手动构建ServiceResponse。同时补充验证通过后注入用户结构体的逻辑:

修改后的完整代码

use actix_service::Service;
use actix_web::error::ResponseError;
use actix_web::{
    dev::{ServiceRequest, ServiceResponse},
    Error, HttpMessage, Result,
};
use actix_web::{http, HttpResponse};
use futures::future::{ok, Ready};
use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation, TokenData};
use serde::Deserialize;
use std::pin::Pin;
use std::{env, fmt};

// JWT Claims structure
#[derive(Debug, Deserialize, Clone)]
struct Claims {
    user_id: String,
    user_name: String,
    exp: usize,
    iat: usize,
    iss: String,
    aud: String,
    sub: String,
    typ: String,
    jti: String,
    role: String,
    permission: String,
}

#[derive(Debug)]
struct UnauthorizedError {
    message: String,
}

impl UnauthorizedError {
    fn new(message: &str) -> Self {
        UnauthorizedError {
            message: message.to_string(),
        }
    }
}

impl ResponseError for UnauthorizedError {
    fn status_code(&self) -> http::StatusCode {
        http::StatusCode::UNAUTHORIZED
    }

    fn error_response(&self) -> HttpResponse {
        HttpResponse::Unauthorized().body(self.message.clone())
    }
}

impl fmt::Display for UnauthorizedError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "Unauthorized: {}", self.message)
    }
}

pub struct JwtMiddleware;

impl<S, B> actix_service::Transform<S, ServiceRequest> for JwtMiddleware
where
    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + Clone,
{
    type Response = ServiceResponse<B>;
    type Error = Error;
    type Transform = JwtMiddlewareMiddleware<S>;
    type InitError = ();
    type Future = Ready<Result<Self::Transform, Self::InitError>>;

    fn new_transform(&self, service: S) -> Self::Future {
        ok(JwtMiddlewareMiddleware { service })
    }
}

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

impl<S, B> Service<ServiceRequest> for JwtMiddlewareMiddleware<S>
where
    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + Clone,
{
    type Response = ServiceResponse<B>;
    type Error = Error;
    type Future = Pin<Box<dyn futures::Future<Output = Result<Self::Response, Self::Error>>>>;

    fn poll_ready(
        &self,
        cx: &mut std::task::Context<'_>,
    ) -> std::task::Poll<Result<(), Self::Error>> {
        self.service.poll_ready(cx)
    }

    fn call(&self, mut req: ServiceRequest) -> Self::Future {
        // Extract the Authorization header
        let auth_header = req.headers().get("Authorization");

        let result = match auth_header {
            Some(token) => match token.to_str() {
                Ok(token_str) if token_str.starts_with("Bearer ") => {
                    let jwt = token_str[7..].to_string();
                    let secret_key = env::var("SECRET_KEY").expect("SECRET_KEY not set");
                    let secret = secret_key.as_bytes();

                    let decoding_key = DecodingKey::from_secret(secret);
                    let validation = Validation {
                        algorithms: vec![Algorithm::HS256],
                        ..Default::default()
                    };

                    match decode::<Claims>(&jwt, &decoding_key, &validation) {
                        Ok(token_data) => {
                            // 将用户Claims注入请求的extensions中
                            req.extensions_mut().insert(token_data.claims);
                            Ok(req)
                        }
                        Err(_) => Err(UnauthorizedError::new("Access denied")),
                    }
                }
                Ok(_) => Err(UnauthorizedError::new("Token must start with Bearer")),
                Err(_) => Err(UnauthorizedError::new("Invalid token format")),
            },
            None => Err(UnauthorizedError::new("Authorization header missing")),
        };

        match result {
            Ok(req) => {
                // 验证通过,继续执行后续服务
                let fut = self.service.call(req);
                Box::pin(async move { Ok(fut.await?) })
            }
            Err(err) => {
                // 验证失败,返回错误响应
                Box::pin(async move { Ok(req.error_response(err)) })
            }
        } as Self::Future
    }
}

关键修改说明

  1. 修复类型不匹配:移除所有手动创建HttpResponse和ServiceResponse::new的代码,直接返回req.error_response(err),该方法会自动生成符合ServiceResponse<B>类型的响应
  2. 注入用户结构体:验证成功后,将解析得到的Claims通过req.extensions_mut().insert()注入请求,后续handler可以通过req.extensions().get::<Claims>()获取用户信息
  3. 优化错误处理:简化分支逻辑,统一错误返回路径,同时为UnauthorizedError实现error_response方法,自定义错误响应内容
  4. 完善错误信息:细化不同错误场景的提示信息,便于调试

内容的提问来源于stack exchange,提问作者Biswas Sampad

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 05:27:04