feat: 完善火山云API签名算法实现

- 添加完整的HMAC-SHA256签名算法
- 实现规范请求(CanonicalRequest)构建
- 实现待签名字符串(StringToSign)创建
- 实现签名密钥派生(kSigning)算法
- 实现最终签名计算和Authorization头构建
- 添加必要的依赖: hmac, sha2, hex, chrono, url
- 严格按照火山云API文档规范实现签名流程

技术细节:
- 支持POST请求的JSON body哈希计算
- 规范化查询字符串和请求头处理
- 多层HMAC密钥派生: kDate -> kRegion -> kService -> kSigning
- 使用cn-north-1地区和cv服务标识符
- 生成标准格式的Authorization请求头
This commit is contained in:
imeepos 2025-07-31 12:49:40 +08:00
parent 73149f4101
commit 09b79d55df
2 changed files with 153 additions and 17 deletions

View File

@ -49,6 +49,10 @@ num_cpus = "1.16"
tokio-tungstenite = "0.20" tokio-tungstenite = "0.20"
futures-util = "0.3" futures-util = "0.3"
rand = "0.8" rand = "0.8"
hmac = "0.12.1"
sha2 = "0.10.9"
hex = "0.4.3"
url = "2.5.4"
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
winapi = { version = "0.3", features = ["sysinfoapi"] } winapi = { version = "0.3", features = ["sysinfoapi"] }

View File

@ -4,6 +4,11 @@ use serde::{Deserialize, Serialize};
use std::sync::Arc; use std::sync::Arc;
use tracing::{error, info, warn}; use tracing::{error, info, warn};
use uuid::Uuid; use uuid::Uuid;
use chrono::{DateTime, Utc};
use hmac::{Hmac, Mac};
use sha2::{Digest, Sha256};
use hex;
use url::Url;
use crate::data::models::video_generation_record::{ use crate::data::models::video_generation_record::{
VideoGenerationRecord, CreateVideoGenerationRequest, VideoGenerationQuery VideoGenerationRecord, CreateVideoGenerationRequest, VideoGenerationQuery
@ -227,12 +232,10 @@ impl VolcanoVideoService {
let api_url = "https://visual.volcengineapi.com?Action=CVSubmitTask&Version=2022-08-31"; let api_url = "https://visual.volcengineapi.com?Action=CVSubmitTask&Version=2022-08-31";
// 构建认证头 // 构建认证头
let timestamp = std::time::SystemTime::now() let now = Utc::now();
.duration_since(std::time::UNIX_EPOCH) let timestamp = now.format("%Y%m%dT%H%M%SZ").to_string();
.unwrap() let date = now.format("%Y%m%d").to_string();
.as_secs() let auth_header = self.build_auth_header("POST", api_url, &timestamp, &date, &request_body)?;
.to_string();
let auth_header = self.build_auth_header("POST", api_url, &timestamp, &request_body)?;
let response = self.http_client let response = self.http_client
.post(api_url) .post(api_url)
@ -340,12 +343,10 @@ impl VolcanoVideoService {
let api_url = "https://visual.volcengineapi.com?Action=CVGetResult&Version=2022-08-31"; let api_url = "https://visual.volcengineapi.com?Action=CVGetResult&Version=2022-08-31";
// 构建认证头 // 构建认证头
let timestamp = std::time::SystemTime::now() let now = Utc::now();
.duration_since(std::time::UNIX_EPOCH) let timestamp = now.format("%Y%m%dT%H%M%SZ").to_string();
.unwrap() let date = now.format("%Y%m%d").to_string();
.as_secs() let auth_header = self.build_auth_header("POST", api_url, &timestamp, &date, &request_body)?;
.to_string();
let auth_header = self.build_auth_header("POST", api_url, &timestamp, &request_body)?;
let response = self.http_client let response = self.http_client
.post(api_url) .post(api_url)
@ -372,11 +373,28 @@ impl VolcanoVideoService {
} }
/// 构建火山云API认证头 /// 构建火山云API认证头
fn build_auth_header<T: Serialize>(&self, _method: &str, _url: &str, _timestamp: &str, _body: &T) -> Result<String> { fn build_auth_header<T: Serialize>(&self, method: &str, url: &str, timestamp: &str, date: &str, body: &T) -> Result<String> {
// 简化的认证实现,实际项目中需要按照火山云文档实现完整的签名算法 // 1. 创建规范请求 (CanonicalRequest)
// 这里先返回基本的认证头格式 let canonical_request = self.create_canonical_request(method, url, timestamp, body)?;
let auth_string = format!("HMAC-SHA256 Credential={}, SignedHeaders=content-type;host;x-date, Signature=placeholder",
self.vol_access_key); // 2. 创建待签名字符串 (StringToSign)
let credential_scope = format!("{}/cn-north-1/cv/request", date);
let string_to_sign = self.create_string_to_sign(timestamp, &credential_scope, &canonical_request)?;
// 3. 派生签名密钥 (kSigning)
let signing_key = self.derive_signing_key(&self.vol_secret_key, date, "cn-north-1", "cv")?;
// 4. 计算签名 (Signature)
let signature = self.calculate_signature(&signing_key, &string_to_sign)?;
// 5. 构建Authorization头
let auth_string = format!(
"HMAC-SHA256 Credential={}/{}, SignedHeaders=host;x-date, Signature={}",
self.vol_access_key,
credential_scope,
signature
);
Ok(auth_string) Ok(auth_string)
} }
@ -402,6 +420,120 @@ impl VolcanoVideoService {
} }
Ok(()) Ok(())
} }
/// 创建规范请求
fn create_canonical_request<T: Serialize>(&self, method: &str, url: &str, timestamp: &str, body: &T) -> Result<String> {
// 解析URL获取host和path
let parsed_url = Url::parse(url)?;
let host = parsed_url.host_str().ok_or_else(|| anyhow!("无效的URL"))?;
let path = parsed_url.path();
let query = parsed_url.query().unwrap_or("");
// 规范化查询字符串
let canonical_query_string = self.canonicalize_query_string(query);
// 规范化请求头
let canonical_headers = format!("host:{}\nx-date:{}\n", host, timestamp);
// 签名的请求头
let signed_headers = "host;x-date";
// 计算请求体的哈希值
let body_json = serde_json::to_string(body)?;
let body_hash = hex::encode(Sha256::digest(body_json.as_bytes()));
// 构建规范请求
let canonical_request = format!(
"{}\n{}\n{}\n{}\n{}\n{}",
method,
path,
canonical_query_string,
canonical_headers,
signed_headers,
body_hash
);
Ok(canonical_request)
}
/// 规范化查询字符串
fn canonicalize_query_string(&self, query: &str) -> String {
if query.is_empty() {
return String::new();
}
let mut params: Vec<(String, String)> = query
.split('&')
.filter_map(|param| {
let mut parts = param.splitn(2, '=');
let key = parts.next()?.to_string();
let value = parts.next().unwrap_or("").to_string();
Some((key, value))
})
.collect();
// 按参数名称的ASCII升序排序
params.sort_by(|a, b| a.0.cmp(&b.0));
// 构建规范化查询字符串
params
.into_iter()
.map(|(key, value)| format!("{}={}", key, value))
.collect::<Vec<_>>()
.join("&")
}
/// 创建待签名字符串
fn create_string_to_sign(&self, timestamp: &str, credential_scope: &str, canonical_request: &str) -> Result<String> {
let canonical_request_hash = hex::encode(Sha256::digest(canonical_request.as_bytes()));
let string_to_sign = format!(
"HMAC-SHA256\n{}\n{}\n{}",
timestamp,
credential_scope,
canonical_request_hash
);
Ok(string_to_sign)
}
/// 派生签名密钥
fn derive_signing_key(&self, secret_key: &str, date: &str, region: &str, service: &str) -> Result<Vec<u8>> {
type HmacSha256 = Hmac<Sha256>;
// kDate = HMAC(kSecret, Date)
let mut mac = HmacSha256::new_from_slice(secret_key.as_bytes())?;
mac.update(date.as_bytes());
let k_date = mac.finalize().into_bytes();
// kRegion = HMAC(kDate, Region)
let mut mac = HmacSha256::new_from_slice(&k_date)?;
mac.update(region.as_bytes());
let k_region = mac.finalize().into_bytes();
// kService = HMAC(kRegion, Service)
let mut mac = HmacSha256::new_from_slice(&k_region)?;
mac.update(service.as_bytes());
let k_service = mac.finalize().into_bytes();
// kSigning = HMAC(kService, "request")
let mut mac = HmacSha256::new_from_slice(&k_service)?;
mac.update(b"request");
let k_signing = mac.finalize().into_bytes();
Ok(k_signing.to_vec())
}
/// 计算签名
fn calculate_signature(&self, signing_key: &[u8], string_to_sign: &str) -> Result<String> {
type HmacSha256 = Hmac<Sha256>;
let mut mac = HmacSha256::new_from_slice(signing_key)?;
mac.update(string_to_sign.as_bytes());
let signature = mac.finalize().into_bytes();
Ok(hex::encode(signature))
}
} }
// 实现Clone trait以支持异步任务 // 实现Clone trait以支持异步任务