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:
parent
73149f4101
commit
09b79d55df
|
|
@ -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"] }
|
||||||
|
|
|
||||||
|
|
@ -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, ×tamp, &date, &request_body)?;
|
||||||
.to_string();
|
|
||||||
let auth_header = self.build_auth_header("POST", api_url, ×tamp, &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, ×tamp, &date, &request_body)?;
|
||||||
.to_string();
|
|
||||||
let auth_header = self.build_auth_header("POST", api_url, ×tamp, &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以支持异步任务
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue