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

如何在Tower层中获取请求体以对gRPC请求进行签名?

解决方案

核心原理:http::Request的BoxBody是流式异步迭代器,要获取完整请求体必须先异步消费全部数据,消费后重新构造新的Body塞回请求再转发即可,无需克隆请求,原地重组成本极低。

前置依赖

先确认Cargo.toml中添加以下依赖:

bytes = "1.0"
http-body = "0.4"
futures-util = "0.3"
hmac = "0.12"
sha2 = "0.10"

修改后的完整代码

use bytes::Bytes;
use http::{Request, header::HeaderValue};
use std::task::{Context, Poll};
use tonic::body::BoxBody;
use http_body::Body as HttpBody;
use tower::{Layer, Service};
use std::future::Future;
use std::pin::Pin;

pub struct AuthLayer {
    hmac_key: Vec<u8>,
}

impl AuthLayer {
    pub fn new(hmac_key: Vec<u8>) -> Self {
        AuthLayer { hmac_key }
    }
}

impl<S> Layer<S> for AuthLayer {
    type Service = AuthService<S>;

    fn layer(&self, inner: S) -> Self::Service {
        AuthService {
            hmac_key: self.hmac_key.clone(),
            inner,
        }
    }
}

pub struct AuthService<S> {
    hmac_key: Vec<u8>,
    inner: S,
}

impl<S> Service<Request<BoxBody>> for AuthService<S>
where
    S: Service<Request<BoxBody>> + Clone + Send + 'static,
    S::Future: Send + 'static,
    S::Error: std::error::Error + Send + Sync + 'static,
{
    type Response = S::Response;
    type Error = S::Error;
    type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;

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

    fn call(&mut self, request: Request<BoxBody>) -> Self::Future {
        let hmac_key = self.hmac_key.clone();
        let inner = self.inner.clone();

        Box::pin(async move {
            // 拆分请求为头部和Body
            let (mut parts, body) = request.into_parts();
            
            // 缓冲整个Body到内存
            let body_bytes = http_body::to_bytes(body)
                .await
                .map_err(|e| e.into())?;
            
            // 计算请求体的HMAC值,可替换为你实际使用的算法
            let hmac = compute_hmac(&hmac_key, &body_bytes);
            
            // 将HMAC写入请求头,二进制值加-bin后缀符合gRPC metadata规范
            parts.headers.insert(
                "x-request-hmac-bin",
                HeaderValue::from_bytes(&hmac).expect("非法HMAC值"),
            );
            
            // 用缓冲好的字节重新构造BoxBody
            let new_body = BoxBody::new(http_body::Full::new(body_bytes));
            
            // 组装新的请求
            let new_request = Request::from_parts(parts, new_body);
            
            // 转发给内层服务处理
            inner.call(new_request).await
        })
    }
}

// 示例HMAC-SHA256计算函数
fn compute_hmac(key: &[u8], data: &[u8]) -> Vec<u8> {
    use hmac::{Hmac, Mac};
    use sha2::Sha256;
    type HmacSha256 = Hmac<Sha256>;
    
    let mut mac = HmacSha256::new_from_slice(key).expect("HMAC密钥长度非法");
    mac.update(data);
    mac.finalize().into_bytes().to_vec()
}

注意事项

  • 该方案仅适用于非流式gRPC请求(Unary调用),流式请求不要全量缓冲Body
  • 如果你的内层Service不实现Clone,可以用tower::ServiceExt::ready等待服务就绪后再调用,避免poll_ready的校验失效
  • 如果HMAC需要传输为字符串格式,去掉header名的-bin后缀,把HMAC转为base64/hex字符串后再写入header即可

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.04 07:54:01