带参数与签名的请求提交异常:签名无效错误排查求助
解决Binance期货API签名无效错误(Code -1022)
收到错误:
{"code":-1022,"msg":"Signature for this request is not valid."}
已确认API密钥/密钥正确,也验证过签名计算逻辑,但怀疑参数与签名的传递步骤有问题,代码如下:
#[tokio::main] async fn main() -> Result<(), Box<dyn Error>> { let (symbol, side, trade_type, quantity, price, time_inforce, api_key, api_secret) = ( "BTCUSDT", "BUY", "LIMIT", "0.1", "20000.0", "GTC", "my-key", "my-secret", ); let client = reqwest::Client::new(); let mut headers_map = reqwest::header::HeaderMap::new(); headers_map.insert("Content-Type", "application/json".parse().unwrap()); headers_map.insert("X-MBX-APIKEY", api_key.parse().unwrap()); let mut param_map = std::collections::HashMap::new(); param_map.insert("symbol", symbol); param_map.insert("side", side); param_map.insert("type", trade_type); param_map.insert("quantity", quantity); let current_timestamp = chrono::Utc::now().timestamp_millis().to_string(); param_map.insert("timestamp", current_timestamp.as_str()); if trade_type == "LIMIT" { param_map.insert("price", price); param_map.insert("timeInForce", time_inforce); } let mut query = String::new(); for (key, value) in ¶m_map { query.push_str(&format!("{}={}&", key, value)); } query.pop(); // remove trailing '&' let signature = { type HmacSha256 = hmac::Hmac<sha2::Sha256>; let mut mac = HmacSha256::new_from_slice(api_secret.as_bytes()) .expect("HMAC can take key of any size"); mac.update(query.as_bytes()); hex::encode(mac.finalize().into_bytes()) // format!("{:02x}", mac.finalize().into_bytes()) }; let res = client .post("https://fapi.binance.com/fapi/v1/order") // fapi/v1/order/test: for test orders .headers(headers_map) .body(query) .query(&[("timestamp", ¤t_timestamp), ("signature", &signature)]) .send() .await? .text() .await?; Ok(()) }
问题分析与解决方法
参数传递格式错误:Binance签名API要求POST请求使用
application/x-www-form-urlencoded格式,而非JSON。同时当前代码把参数拆分到body和query中,导致实际发送的参数集合和计算签名时的不一致(重复了timestamp)。参数顺序问题:
HashMap是无序的,Binance要求参数按ASCII升序排列后再生成签名,无序遍历会导致签名计算错误。签名传递方式错误:签名应该和所有其他参数一起放在form body里,不能分开在query中传递。
修正后的代码
#[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { let (symbol, side, trade_type, quantity, price, time_inforce, api_key, api_secret) = ( "BTCUSDT", "BUY", "LIMIT", "0.1", "20000.0", "GTC", "my-key", "my-secret", ); let client = reqwest::Client::new(); let mut headers_map = reqwest::header::HeaderMap::new(); // 改为Binance要求的表单编码格式 headers_map.insert("Content-Type", "application/x-www-form-urlencoded".parse().unwrap()); headers_map.insert("X-MBX-APIKEY", api_key.parse().unwrap()); // 使用BTreeMap保证参数按ASCII升序排列 let mut param_map = std::collections::BTreeMap::new(); param_map.insert("symbol", symbol); param_map.insert("side", side); param_map.insert("type", trade_type); param_map.insert("quantity", quantity); let current_timestamp = chrono::Utc::now().timestamp_millis().to_string(); param_map.insert("timestamp", ¤t_timestamp); if trade_type == "LIMIT" { param_map.insert("price", price); param_map.insert("timeInForce", time_inforce); } // 生成待签名的参数字符串(包含URL编码) let query = param_map .iter() .map(|(k, v)| format!("{}={}", urlencoding::encode(k), urlencoding::encode(v))) .collect::<Vec<_>>() .join("&"); // 计算签名 let signature = { type HmacSha256 = hmac::Hmac<sha2::Sha256>; let mut mac = HmacSha256::new_from_slice(api_secret.as_bytes()) .expect("HMAC can take key of any size"); mac.update(query.as_bytes()); hex::encode(mac.finalize().into_bytes()) }; // 拼接最终的表单数据(包含签名) let final_form_data = format!("{}&signature={}", query, signature); let res = client .post("https://fapi.binance.com/fapi/v1/order") // 测试可改用fapi/v1/order/test .headers(headers_map) .body(final_form_data) .send() .await? .text() .await?; println!("{}", res); Ok(()) }
关键改动说明
- 替换
HashMap为BTreeMap,确保参数按ASCII升序排列,符合Binance的签名要求。 - 将
Content-Type改为application/x-www-form-urlencoded,匹配API的接收格式。 - 使用
urlencoding对参数进行URL编码,避免特殊字符导致的签名错误。 - 把签名加入到表单数据中,统一通过body发送,不再拆分到query参数。
- 确保计算签名的参数字符串和实际发送的完全一致。
内容的提问来源于stack exchange,提问作者Henry
相关产品推荐
相关产品推荐

