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

Shopify API文件上传时为何要求Authorization头?

解决Shopify API文件上传的MissingSecurityHeader错误

问题描述

我通过Shopify API上传文件分两步操作:

    1. 请求临时上传目录,可正常获取所需参数
    1. 上传文件时收到错误:
MissingSecurityHeaderYour request was missing a required header.
Authorization

根据Shopify官方文档,上传无需Authorization头,且文档中的curl示例也未使用该头,为何我的请求会被要求提供该头?以下是我的C#代码:

using Newtonsoft.Json;
using ShopifyBulkUploadPoc;

var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://xxx.myshopify.com/admin/api/2021-07/graphql.json");
request.Headers.Add("X-Shopify-Access-Token", "xxx");
request.Headers.Add("Cookie", "_master_udr=xxx; _shopify_y=xxx; localization=DK; secure_customer_sig=");
var content = new StringContent("{\"query\":\"mutation {\\r\\n  stagedUploadsCreate(input:{\\r\\n    resource: BULK_MUTATION_VARIABLES,\\r\\n    filename: \\\"bulk_op_vars\\\",\\r\\n    mimeType: \\\"text/jsonl\\\",\\r\\n    httpMethod: POST\\r\\n  }){\\r\\n    userErrors{\\r\\n      field,\\r\\n      message\\r\\n    },\\r\\n    stagedTargets{\\r\\n      url,\\r\\n      resourceUrl,\\r\\n      parameters {\\r\\n        name,\\r\\n        value\\r\\n      }\\r\\n    }\\r\\n  }\\r\\n}\",\"variables\":{}}", null, "application/json");
request.Content = content;
var response = await client.SendAsync(request);
response.EnsureSuccessStatusCode();

var responseContent = await response.Content.ReadAsStringAsync();

Rootobject rootObject = JsonConvert.DeserializeObject<Rootobject>(responseContent);

var url = rootObject.data.stagedUploadsCreate.stagedTargets[0].url;
var key = rootObject.data.stagedUploadsCreate.stagedTargets[0].parameters[3].value;
var acl = rootObject.data.stagedUploadsCreate.stagedTargets[0].parameters[2].value;
var x_goog_date = rootObject.data.stagedUploadsCreate.stagedTargets[0].parameters[4].value;
var x_goog_credential = rootObject.data.stagedUploadsCreate.stagedTargets[0].parameters[5].value;
var x_goog_algorithm = rootObject.data.stagedUploadsCreate.stagedTargets[0].parameters[6].value;
var x_goog_signature = rootObject.data.stagedUploadsCreate.stagedTargets[0].parameters[7].value;
var policy = rootObject.data.stagedUploadsCreate.stagedTargets[0].parameters[8].value;
var content_type = rootObject.data.stagedUploadsCreate.stagedTargets[0].parameters[0].value;
var success_action_status = rootObject.data.stagedUploadsCreate.stagedTargets[0].parameters[1].value;

var requestB = new HttpRequestMessage(HttpMethod.Put, "https://shopify-staged-uploads.storage.googleapis.com/");
var contentB = new MultipartFormDataContent();

contentB.Add(new StringContent(key), "key");
contentB.Add(new StringContent(x_goog_credential), "x-goog-credential");
contentB.Add(new StringContent(x_goog_algorithm), "x-goog-algorithm");
contentB.Add(new StringContent(x_goog_date), "x-goog-date");
contentB.Add(new StringContent(x_goog_signature), "x-goog-signature");
contentB.Add(new StringContent(policy), "policy");
contentB.Add(new StringContent(acl), "acl");
contentB.Add(new StringContent(content_type), "Content-Type");
contentB.Add(new StringContent(success_action_status), "success_action_status");

contentB.Add(new StreamContent(File.OpenRead("C:/products.jsonl")), "file", "/C:/products.jsonl");

requestB.Content = content;

var responseB = await client.SendAsync(requestB);
Console.WriteLine(await responseB.Content.ReadAsStringAsync());

namespace ShopifyBulkUploadPoc
{
    public class Rootobject
    {
        public Data data { get; set; }
    }

    public class Data
    {
        public Stageduploadscreate stagedUploadsCreate { get; set; }
    }

    public class Stageduploadscreate
    {
        public object[] userErrors { get; set; }
        public Stagedtarget[] stagedTargets { get; set; }
    }

    public class Stagedtarget
    {
        public string url { get; set; }
        public string resourceUrl { get; set; }
        public Parameter[] parameters { get; set; }
    }

    public class Parameter
    {
        public string name { get; set; }
        public string value { get; set; }
    }
}

问题分析与修复

你遇到的错误并非真的缺少Authorization头,而是代码里的两个致命错误导致请求完全不符合要求:

  1. 请求方法与URL错误

    • 第一步创建staged upload时指定了httpMethod: POST,但第二步你用了HttpMethod.Put,方法不匹配会导致签名验证失败。
    • 上传URL不能直接写根地址,必须使用第一步返回的url字段(包含签名路径的完整上传地址)。
  2. 内容赋值错误

    • 最后一行requestB.Content = content;是严重错误:你把第一步的GraphQL请求内容赋值给了上传请求,而不是刚构建的带签名参数的contentB,导致上传的内容完全不对,自然触发安全验证错误。

另外,硬编码参数索引(比如parameters[3])很危险,参数顺序可能会变化,建议通过参数名匹配取值。

修正后的关键代码

// 1. 用第一步返回的完整上传URL,且请求方法和stagedUploadsCreate里的httpMethod一致
var requestB = new HttpRequestMessage(HttpMethod.Post, url);
var contentB = new MultipartFormDataContent();

// 2. 改用参数名匹配取值,避免索引硬编码
var parametersDict = rootObject.data.stagedUploadsCreate.stagedTargets[0].parameters
    .ToDictionary(p => p.name, p => p.value);

contentB.Add(new StringContent(parametersDict["key"]), "key");
contentB.Add(new StringContent(parametersDict["x-goog-credential"]), "x-goog-credential");
contentB.Add(new StringContent(parametersDict["x-goog-algorithm"]), "x-goog-algorithm");
contentB.Add(new StringContent(parametersDict["x-goog-date"]), "x-goog-date");
contentB.Add(new StringContent(parametersDict["x-goog-signature"]), "x-goog-signature");
contentB.Add(new StringContent(parametersDict["policy"]), "policy");
contentB.Add(new StringContent(parametersDict["acl"]), "acl");
contentB.Add(new StringContent(parametersDict["Content-Type"]), "Content-Type");
contentB.Add(new StringContent(parametersDict["success_action_status"]), "success_action_status");

// 3. 文件字段只需要文件名,不需要完整路径
contentB.Add(new StreamContent(File.OpenRead("C:/products.jsonl")), "file", "products.jsonl");

// 4. 正确赋值上传内容
requestB.Content = contentB;

var responseB = await client.SendAsync(requestB);
Console.WriteLine(await responseB.Content.ReadAsStringAsync());

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 06:14:51