You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Azure Monitor数据收集器API签名一致却返回InvalidAuthorization问题

Azure Monitor数据收集器API 403 InvalidAuthorization问题排查(非签名本身原因)

我将Azure Monitor数据收集器API文档中的Python签名示例转换为Rust代码后,Rust程序生成的SharedKey <workspace_id>:<signature>调用API时被返回403状态码,响应体内容为:

{"Error":"InvalidAuthorization","Message":"An invalid signature was specified in the Authorization header"}

但如果把Rust程序运行时生成的RFC1123格式日期硬编码到Python示例脚本中,生成的Authorization头与Rust程序完全一致,且能成功调用API发送日志。既然两者签名完全一致,说明问题并非出在签名本身,想了解还有哪些原因会导致该API返回此类错误。


补充:可正常运行的Python示例

import requests
import datetime
import hashlib
import hmac
import base64

WORKSPACE_ID = "<my workspace ID>"
SHARED_KEY = "<my primary key>"

def build_signature(message, secret):
    key_bytes = base64.b64decode(secret)
    message_bytes = bytes(message, encoding="utf-8")
    hmacsha256 = hmac.new(key_bytes, message_bytes, digestmod=hashlib.sha256).digest()
    encoded_hash = base64.b64encode(hmacsha256).decode()
    return encoded_hash

def post_data():
    data = '{"hello":"world"}'
    date_string = datetime.datetime.utcnow().strftime('%a, %d %b %Y %H:%M:%S GMT')
    # date_string = "Thu, 14 Sep 2023 01:27:12 GMT"
    content_length = len(data)
    string_to_hash = f"POST\n{content_length}\napplication/json\nx-ms-date:{date_string}\n/api/logs"
    hashed_string = build_signature(string_to_hash, SHARED_KEY)
    signature = f"SharedKey {WORKSPACE_ID}:{hashed_string}"
    print(signature)

    query = "api-version=2016-04-01"; 
    url = f"https://{WORKSPACE_ID}.ods.opinsights.azure.com/api/logs?{query}"

    headers = {
        'content-type': "application/json",
        'Authorization': signature,
        'Log-Type': "my-event-type",
        'x-ms-date': date_string
    }
    response = requests.post(url, data=data, headers=headers)
    if (response.status_code >= 200 and response.status_code <= 299):
        print('Accepted')
    else:
        print("Response: {}".format(response.content))
        print("Response code: {}".format(response.status_code))

if __name__ == "__main__":
    post_data()

生成相同签名但返回错误的Rust代码

use base64;
use chrono::Utc;
use ring::hmac;
use reqwest;
use std::error::Error;

#[derive(Clone, Debug)]
enum MyError {
    MessageSendError(String),
}

impl std::fmt::Display for MyError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            MyError::MessageSendError(msg) => write!(f, "{}", msg),
        }
    }
}

impl Error for MyError {}

struct SentinelClient {
    azure_collector_url: String,
    workspace_id: String,
    shared_key: String,
    http_client: reqwest::Client,
}

impl SentinelClient {
    fn new(workspace_id: String, shared_key: String) -> Self {
        let query = "api-version=2016-04-01"; 
        let azure_collector_url =
            format!("https://{workspace_id}.ods.opinsights.azure.com/api/logs?{query}");
        Self {
            azure_collector_url,
            workspace_id,
            shared_key,
            http_client: reqwest::Client::new(),
        }
    }

    async fn send(&self, data: String) -> Result<(), MyError> {
        let workspace_id = &self.workspace_id;
        let date_string = format!("{}", Utc::now().format("%a, %d %b %Y %H:%M:%S GMT"));
        println!("date string {}", date_string);
        let content_length = data.to_string().len();
        let string_to_hash =
            format!("POST\n{content_length}\napplication/json\nx-ms-date:{date_string}\n/api/logs");
        let hashed_string = Self::build_signature(&string_to_hash, &self.shared_key)?;
        let signature = format!("SharedKey {workspace_id}:{hashed_string}");
        println!("{}", signature);

        let url = &self.azure_collector_url;
        let request = self
            .http_client
            .post(url)
            .json(&data)
            .header("Authorization", signature)
            .header("Log-Type", "my-event-type")
            .header("x-ms-date", date_string);

        match request.send().await {
            Ok(resp) => {
                if resp.status().is_success() {
                    println!("request successful: {:?}", &resp);
                    Ok(())
                } else {
                    let body = resp
                        .text()
                        .await
                        .map_err(|e| MyError::MessageSendError(e.to_string()))?;
                    println!("request unsuccessful: {body}");
                    Err(MyError::MessageSendError(body))
                }
            }
            Err(e) => Err(MyError::MessageSendError(e.to_string())),
        }
    }

    /// Build the API signature for the Azure Monitor HTTP Data Collector
    /// authorization header.
    fn build_signature(message: &str, secret: &str) -> Result<String, MyError> {
        let key_bytes = base64::decode(secret).map_err(|e| {
            MyError::MessageSendError(format!("could not decode shared key: {e}"))
        })?;
        let message_bytes = message.as_bytes();
        let hmacsha256 = hmac::Key::new(hmac::HMAC_SHA256, &key_bytes);
        let encoded_hash = base64::encode(hmac::sign(&hmacsha256, message_bytes).as_ref());

        Ok(encoded_hash)
    }
}

(注:修复了原Rust代码中缺失的async关键字、错误枚举的Display和Error实现)


可能的原因及修复方案

1. 请求体序列化方式导致Content-Length不匹配

Python代码中使用data=data直接发送JSON字符串,Content-Length是原字符串的实际长度;而Rust代码中使用.json(&data),会将传入的String类型序列化为转义后的JSON字符串(比如原数据{"hello":"world"}会被转成"{\"hello\":\"world\"}"),导致实际发送的Content-Length远大于签名计算时的长度。

Azure的签名验证会严格检查签名中的Content-Length与实际请求的Content-Length是否一致,不一致就会返回授权错误。

修复方案:
将Rust中的.json(&data)改为.body(data),并手动添加content-type请求头:

let request = self
    .http_client
    .post(url)
    .body(data)
    .header("content-type", "application/json")
    .header("Authorization", signature)
    .header("Log-Type", "my-event-type")
    .header("x-ms-date", date_string);

2. 请求头的自动处理差异

reqwest可能会自动添加一些额外请求头(如Accept、Accept-Encoding),但这通常不会影响授权验证。不过需要确保:

  • x-ms-date头的大小写完全正确(必须是小写的x-ms-date)
  • Authorization头没有多余的空格或换行符

3. 系统时间偏差

虽然你用了UTC时间,但如果Rust程序运行的系统时间与真实UTC时间偏差超过5分钟,Azure的授权验证也会失败。不过你将Rust生成的日期硬编码到Python中能成功,说明该日期是有效的,这个可能性较低,但仍可确认系统时间是否同步。


内容的提问来源于stack exchange,提问作者user4601931

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 22:34:55