首页 > 解决方案 > Warp 中的 API 密钥验证 (Rust)

问题描述

我正在尝试开始使用 warp 并测试 api 密钥验证。以下代码有效,但效果不佳。

验证函数从标头中提取密钥。成功验证后,不再使用密钥,但“handle_request”函数需要为其输入参数。

您能否就如何避免不需要的输入参数和使用 warp 进行 api 密钥验证的更简洁方法提出建议?

提前致谢!

use std::convert::Infallible;
use std::error::Error;
use serde::{Deserialize, Serialize};
use warp::http::{Response, StatusCode};
use warp::{reject, Filter, Rejection, Reply};
//use futures::future;
// use headers::{Header, HeaderMapExt};
// use http::header::HeaderValue;
// use http::HeaderMap;

extern crate pretty_env_logger;
#[macro_use] extern crate log;

#[derive(Deserialize, Serialize)]
struct Params {
    key1: String,
    key2: u32,
}

#[derive(Debug)]
struct Unauthorized;

impl reject::Reject for Unauthorized {}

#[tokio::main]
async fn main() {
    pretty_env_logger::init();
    // get /exampel?key1=value&key2=42
    let route1 = warp::get().and(key_validation())
        .and(warp::query::<Params>())
        .and_then(handle_request);
       
    let routes = route1.recover(handle_rejection);
    warp::serve(routes)
        .run(([127, 0, 0, 1], 3030))
        .await;
}

async fn handle_request(api_key:String, params: Params) -> Result<impl warp::Reply, warp::Rejection> {
    Ok(Response::builder().body(format!("key1 = {}, key2 = {}", params.key1, params.key2)))
}

fn key_validation() -> impl Filter<Extract = (String,), Error = Rejection> + Copy {
    warp::header::<String>("x-api-key").and_then(|n: String| async move {
        if n == "test" {
            Ok(n)
        } else {
            Err(reject::custom(Unauthorized))
        }
    })
}

// JSON replies

/// An API error serializable to JSON.
#[derive(Serialize)]
struct ErrorMessage {
    code: u16,
    message: String,
}

// This function receives a `Rejection` and tries to return a custom
// value, otherwise simply passes the rejection along.
async fn handle_rejection(err: Rejection) -> Result<impl Reply, Infallible> {
    let code;
    let message;

    if err.is_not_found() {
        code = StatusCode::NOT_FOUND;
        message = "NOT_FOUND";
    } else if let Some(Unauthorized) = err.find() {
        code = StatusCode::UNAUTHORIZED;
        message = "Invalide API key";
    } else if let Some(_) = err.find::<warp::reject::MethodNotAllowed>() {
       // We can handle a specific error, here METHOD_NOT_ALLOWED,
        // and render it however we want
        code = StatusCode::METHOD_NOT_ALLOWED;
        message = "METHOD_NOT_ALLOWED";
    } else {
        // We should have expected this... Just log and say its a 500
        error!("unhandled rejection: {:?}", err);
        code = StatusCode::INTERNAL_SERVER_ERROR;
        message = "UNHANDLED_REJECTION";
    }

    let json = warp::reply::json(&ErrorMessage {
        code: code.as_u16(),
        message: message.into(),
    });

    Ok(warp::reply::with_status(json, code))
}

更新: 当我尝试避免使用“key_validation”函数提取一些内容时,我收到此错误:

error[E0271]: type mismatch resolving `<warp::filter::and_then::AndThen<impl warp::Filter+Copy, [closure@src/main.rs:44:50: 50:6]> as warp::filter::FilterBase>::Extract == ()`
  --> src/main.rs:43:24
   |
43 | fn key_validation() -> impl Filter<Extract = (), Error = Rejection> + Copy {
   |                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected tuple, found `()`
   |
   = note:  expected tuple `(_,)`
           found unit type `()`

为了解决这个问题,我尝试了:

async fn handle_request(params: Params) -> Result<impl warp::Reply, warp::Rejection> {
    Ok(Response::builder().body(format!("key1 = {}, key2 = {}", params.key1, params.key2)))
}

fn key_validation() -> impl Filter<Extract = ((),), Error = Rejection> + Copy {
    warp::header::<String>("x-api-key").and_then(|n: String| async move {
        if n == "test" {
            Ok(())
        } else {
            Err(reject::custom(Unauthorized))
        }
    })
}

结果是:

error[E0593]: function is expected to take 2 arguments, but it takes 1 argument
  --> src/main.rs:31:19
   |
31 |         .and_then(handle_request);
   |                   ^^^^^^^^^^^^^^ expected function that takes 2 arguments
...
39 | async fn handle_request(params: Params) -> Result<impl warp::Reply, warp::Rejection> {
   | ------------------------------------------------------------------------------------ takes 1 argument
   |
   = note: required because of the requirements on the impl of `warp::generic::Func<((), Params)>` for `fn(Params) -> impl Future {handle_request}`

这些是使用的依赖项:

[dependencies]
log = "0.4"
pretty_env_logger = "0.4"
tokio = { version = "1", features = ["full"] }
warp = "0.3"
serde = { version = "1.0", features = ["derive"] }
futures = { version = "0.3", default-features = false, features = ["alloc"] }

标签: rustapi-keyrust-warp

解决方案


只需让您的方法不提取任何内容:

async fn handle_request(params: Params) -> Result<impl warp::Reply, warp::Rejection> {
    Ok(Response::builder().body(format!("key1 = {}, key2 = {}", params.key1, params.key2)))
}


fn key_validation() -> impl Filter<Extract = (), Error = Rejection> + Copy {
    warp::header::<String>("x-api-key").and_then(|n: String| async move {
        if n == "test" {
            Ok(())
        } else {
            Err(reject::custom(Unauthorized))
        }
    })
}

可能您需要丢弃 key_validation 结果值,使用untuple_one

let route1 = warp::get()
        .and(key_validation())
        .untuple_one()
        .and(warp::query::<Params>())
        .and_then(handle_request);

推荐阅读