自定义Azure Blob Storage上传方案遭遇请求认证失败求助
Azure Blob Storage自定义上传鉴权错误排查
问题背景
尝试使用@azure/storage-blob包实现Azure存储上传功能时,与所有Node.js版本(16、18、20等)均存在冲突,持续抛出node:os module not found错误。改用自定义上传方案后,遇到鉴权失败错误:
Error uploading file to Azure Blob Storage: Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature.
已完成的检查
- 验证Azure账户名、密钥及容器名均正确。
- 打印请求头和授权字符串,显示内容无误。
- 尝试使用
Content-Type和x-ms-date请求头。 - 确保账户密钥已正确解码并用于签名生成。
- 使用了正确的存储端点:
https://accountName.blob.core.windows.net/containerName/folder/subfolder/parking.jpg。
自定义实现代码
const fetch = require('node-fetch'); const crypto = require('crypto'); const { sanitize, getMediasBucketName } = require('../../utils'); const logger = require('../../utils/logger'); class AzureBlobStorage { constructor() { this.accountName = process.env.AZURE_STORAGE_ACCOUNT_NAME; this.accountKey = process.env.AZURE_STORAGE_ACCOUNT_KEY; this.containerName = process.env.AZURE_CONTAINER_NAME; if (!this.accountName || !this.accountKey || !this.containerName) { throw new Error('Azure Storage account name, key, or container name is missing.'); } // Set the endpoint URL for the Blob Storage container this.endpoint = `https://${this.accountName}.blob.core.windows.net/${this.containerName}`; } _generateAuthorizationHeader(method, path, headers) { const canonicalHeaders = Object.keys(headers) .filter(key => key.toLowerCase().startsWith('x-ms-')) .sort() .map(key => `${key.toLowerCase()}:${headers[key]}`) .join('\n'); const stringToSign = [ method, '', headers['Content-Type'] || '', headers['x-ms-date'], canonicalHeaders, `/${this.accountName}/${this.containerName}${path}` ].join('\n'); // Generate the signature using HMAC-SHA256 const decodedKey = Buffer.from(this.accountKey, 'base64'); const signature = crypto.createHmac('sha256', decodedKey).update(stringToSign).digest('base64'); const authorizationHeader = `SharedKey ${this.accountName}:${signature}`; return authorizationHeader; } async storeFileInDirectory({ projectName, fileName, contentBody, contentType, mediaType, }) { try { const safeFileName = sanitize(fileName); const filePath = `/${projectName}/${mediaType}/${safeFileName}`; const url = `${this.endpoint}${filePath}`; const headers = { 'x-ms-date': new Date().toUTCString(), 'Content-Type': contentType, 'Content-Length': contentBody.length, 'x-ms-blob-type': 'BlockBlob', }; headers['Authorization'] = this._generateAuthorizationHeader('PUT', filePath, headers); const response = await fetch(url, { method: 'PUT', headers: headers, body: contentBody, }); if (!response.ok) { throw new Error(`Error uploading file to Azure Blob Storage: ${response.statusText}`); } logger.info(`File uploaded to Azure: ${filePath}`); return { filePath }; } catch (e) { logger.error('Error uploading file to Azure Blob Storage', { errorMessage: e.message, stack: e.stack, params: { projectName, fileName, mediaType }, }); return null; } } }
问题根源与修复方案
核心问题
自定义签名生成的stringToSign完全不符合Azure SharedKey鉴权规范,主要错误点:
- 字段顺序错误:未遵循Azure规定的字段排列逻辑,错误将
x-ms-date放在标准Date头的位置,且遗漏了Content-Length等必填字段。 - Content-Length未纳入签名计算:该字段是
stringToSign的必填组成部分,未加入会直接导致签名不匹配。
修复后的代码
修改_generateAuthorizationHeader方法,严格按照Azure规范构建签名字符串:
const fetch = require('node-fetch'); const crypto = require('crypto'); const { sanitize, getMediasBucketName } = require('../../utils'); const logger = require('../../utils/logger'); class AzureBlobStorage { constructor() { this.accountName = process.env.AZURE_STORAGE_ACCOUNT_NAME; this.accountKey = process.env.AZURE_STORAGE_ACCOUNT_KEY; this.containerName = process.env.AZURE_CONTAINER_NAME; if (!this.accountName || !this.accountKey || !this.containerName) { throw new Error('Azure Storage account name, key, or container name is missing.'); } this.endpoint = `https://${this.accountName}.blob.core.windows.net/${this.containerName}`; } _generateAuthorizationHeader(method, path, headers) { // 规范头处理:转小写、排序、值去除首尾空格 const canonicalHeaders = Object.keys(headers) .filter(key => key.toLowerCase().startsWith('x-ms-')) .sort() .map(key => `${key.toLowerCase()}:${headers[key].trim()}`) .join('\n'); // 严格按照Azure SharedKey规范构建签名字符串 const stringToSign = [ method.toUpperCase(), // HTTP方法强制大写 headers['Content-Encoding'] || '', headers['Content-Language'] || '', headers['Content-Length'] || '', headers['Content-MD5'] || '', headers['Content-Type'] || '', '', // 使用x-ms-date时,标准Date头留空 headers['If-Modified-Since'] || '', headers['If-Match'] || '', headers['If-None-Match'] || '', headers['If-Unmodified-Since'] || '', headers['Range'] || '', canonicalHeaders, `/${this.accountName}/${this.containerName}${path}` ].join('\n'); const decodedKey = Buffer.from(this.accountKey, 'base64'); const signature = crypto.createHmac('sha256', decodedKey).update(stringToSign, 'utf8').digest('base64'); return `SharedKey ${this.accountName}:${signature}`; } async storeFileInDirectory({ projectName, fileName, contentBody, contentType, mediaType, }) { try { const safeFileName = sanitize(fileName); const filePath = `/${projectName}/${mediaType}/${safeFileName}`; const url = `${this.endpoint}${filePath}`; const headers = { 'x-ms-date': new Date().toUTCString(), 'Content-Type': contentType, 'Content-Length': contentBody.length.toString(), // 转为字符串避免格式异常 'x-ms-blob-type': 'BlockBlob', }; headers['Authorization'] = this._generateAuthorizationHeader('PUT', filePath, headers); const response = await fetch(url, { method: 'PUT', headers: headers, body: contentBody, }); if (!response.ok) { // 获取Azure返回的详细错误信息,便于排查 const errorDetails = await response.text(); throw new Error(`Error uploading file to Azure Blob Storage: ${response.statusText}, Details: ${errorDetails}`); } logger.info(`File uploaded to Azure: ${filePath}`); return { filePath }; } catch (e) { logger.error('Error uploading file to Azure Blob Storage', { errorMessage: e.message, stack: e.stack, params: { projectName, fileName, mediaType }, }); return null; } } }
额外优化点
- 获取详细错误信息:请求失败时解析响应文本,Azure会返回具体的鉴权不匹配原因,大幅提升排查效率。
- Content-Length转字符串:避免部分环境下数字类型头信息导致的格式异常。
- HTTP方法大写:Azure要求签名中的HTTP方法为大写,消除大小写不匹配风险。
官方包问题补充
若后续想重新使用@azure/storage-blob,node:os module not found错误通常由包版本与模块解析方式不兼容导致,可尝试:
- 升级包到最新稳定版
- 检查项目是否启用ESM模式,若启用需确保import语法正确,或在package.json中添加
type: "commonjs" - 删除node_modules和package-lock.json后重新安装依赖
内容的提问来源于stack exchange,提问作者Danis
相关产品推荐
相关产品推荐

