如何在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
相关产品推荐
相关产品推荐

