Rust实现Svix Webhook签名验证失败:无法生成正确HMAC签名
Svix Webhook签名验证失败问题
我在模拟Svix Webhook签名验证时,需要将svix-id请求头、svix-timestamp请求头与原始请求Body组合生成签名,要求与svix-signature请求头一致,但当前实现返回"Invalid signature"错误,签名不匹配。
我的实现代码
let svix_id_header = &req .headers() .get("svix-id") .ok_or_else(|| (StatusCode::BAD_REQUEST, "Missing svix-id header".to_string()))? .to_str() .or_else(|_| Err((StatusCode::BAD_REQUEST, "Invalid svix-id header".to_string())))? .to_string(); let svix_timestamp_header = &req .headers() .get("svix-timestamp") .ok_or_else(|| (StatusCode::BAD_REQUEST, "Missing svix-timestamp header".to_string()))? .to_str() .or_else(|_| Err((StatusCode::BAD_REQUEST, "Invalid svix-id header".to_string())))? .to_string(); let svix_signature_header = &req .headers() .get("svix-signature") .ok_or_else(|| (StatusCode::BAD_REQUEST, "Missing svix-signature header".to_string()))? .to_str() .or_else(|_| Err((StatusCode::BAD_REQUEST, "Invalid svix-id header".to_string())))? .to_string(); let body_bytes = to_bytes(req.into_body(), usize::MAX).await.unwrap(); let body_string = String::from_utf8(body_bytes.to_vec()).unwrap(); let secret_bytes = state.webhook_secret.as_bytes(); let signed_content = format!("{}.{}.{}", svix_id_header, svix_timestamp_header, body_string); let mut signature = HmacSha256::new_from_slice(secret_bytes).or_else(|_| Err((StatusCode::INTERNAL_SERVER_ERROR, "Failed to create HMAC".to_string())))?; signature.update(signed_content.as_bytes()); let result = signature.finalize().into_bytes(); let result = base64::encode(&result.to_vec()); if result != *svix_signature_header { return Err((StatusCode::UNAUTHORIZED, "Invalid signature".to_string())); }
问题原因及修正方案
1. Body处理错误(核心问题)
Svix签名要求使用原始请求Body的字节流计算,但你将Body转成String再拼接,可能因编码转换导致字节不一致(比如非UTF-8字符、转义处理等)。需直接用原始字节拼接三个部分。
2. 签名头格式未解析
svix-signature头的格式通常为v1=<base64签名>,需提取等号后的签名值再对比,而非直接使用整个头字符串。
3. 错误提示文案错误
处理svix-timestamp和svix-signature的错误提示误写为"Invalid svix-id header",需修正为对应字段的提示。
修正后的代码
let svix_id_header = &req .headers() .get("svix-id") .ok_or_else(|| (StatusCode::BAD_REQUEST, "Missing svix-id header".to_string()))? .to_str() .or_else(|_| Err((StatusCode::BAD_REQUEST, "Invalid svix-id header".to_string())))?; let svix_timestamp_header = &req .headers() .get("svix-timestamp") .ok_or_else(|| (StatusCode::BAD_REQUEST, "Missing svix-timestamp header".to_string()))? .to_str() .or_else(|_| Err((StatusCode::BAD_REQUEST, "Invalid svix-timestamp header".to_string())))?; // 解析svix-signature头,提取v1版本的签名值 let svix_signature_header = req .headers() .get("svix-signature") .ok_or_else(|| (StatusCode::BAD_REQUEST, "Missing svix-signature header".to_string()))? .to_str() .or_else(|_| Err((StatusCode::BAD_REQUEST, "Invalid svix-signature header".to_string())))?; let expected_signature = svix_signature_header .split('=') .nth(1) .ok_or_else(|| (StatusCode::BAD_REQUEST, "Invalid svix-signature format".to_string()))?; // 获取原始Body字节,不转成String let body_bytes = to_bytes(req.into_body(), usize::MAX).await .map_err(|_| (StatusCode::BAD_REQUEST, "Failed to read request body".to_string()))?; let secret_bytes = state.webhook_secret.as_bytes(); let mut signature = HmacSha256::new_from_slice(secret_bytes) .map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Failed to create HMAC".to_string()))?; // 按顺序更新HMAC:svix-id + 点 + timestamp + 点 + 原始Body字节 signature.update(svix_id_header.as_bytes()); signature.update(b"."); signature.update(svix_timestamp_header.as_bytes()); signature.update(b"."); signature.update(&body_bytes); let result = signature.finalize().into_bytes(); let result = base64::encode(&result); if result != expected_signature { return Err((StatusCode::UNAUTHORIZED, "Invalid signature".to_string())); }
内容的提问来源于stack exchange,提问作者Kadiem Alqazzaz
相关产品推荐
相关产品推荐

