如何解决通过浏览器 javascript 生成 Azure blob 用户委派 SAS 的问题
我一直在尝试创建一个网页以允许我:
- 使用 msal-browser 库生成 Windows Identity 平台登录弹出窗口以获取不记名令牌。
- 使用它从 blob rest api 获取用户委托密钥,以及
- 使用密钥生成用户委托 SAS 并列出我容器的内容。
我已经到了生成 SAS 代码的步骤,但我生成的签名无效。我已经搜索了很多答案,但自己无法确定问题所在,需要一些帮助。
从我获得不记名令牌并检索用户委托密钥(有效)开始:
const blobDelegationKeyEndpoint =
"https://MYACCOUNT.blob.core.windows.net/?restype=service&comp=userdelegationkey";
let sasKeyOID = "";
let sasKeyTID = "";
let sasKeyStart = "";
let sasKeyExpiry = "";
let sasKeyService = "";
let sasKeyVersion = "";
let sasKeyValue = "";
btnDelegationKey.addEventListener("click",async () => {
const headers = new Headers();
headers.append("Authorization",bearer);
headers.append("x-ms-version","2020-06-12");
const options = {
method: "POST",headers: headers,body: `<?xml version="1.0" encoding="utf-8"?>
<KeyInfo>
<Start>2021-03-27T09:20:00Z</Start>
<Expiry>2021-03-27T12:30:00Z</Expiry>
</KeyInfo> `,};
fetch(blobDelegationKeyEndpoint,options)
.then((resp) => {
return resp.text();
})
.then((data) => {
const parser = new DOMParser();
console.log(data);
const xmlDoc = parser.parseFromString(data,"text/xml");
sasKeyOID = xmlDoc.getElementsByTagName("SignedOid")[0].textContent;
sasKeyTID = xmlDoc.getElementsByTagName("SignedTid")[0].textContent;
sasKeyStart = xmlDoc.getElementsByTagName("SignedStart")[0].textContent;
sasKeyExpiry = xmlDoc.getElementsByTagName("SignedExpiry")[0].textContent;
sasKeyService = xmlDoc.getElementsByTagName("SignedService")[0]
.textContent;
sasKeyVersion = xmlDoc.getElementsByTagName("SignedVersion")[0]
.textContent;
sasKeyValue = xmlDoc.getElementsByTagName("Value")[0].textContent;
});
});
接下来我构造我的“StringToSign” - 格式基于 https://docs.microsoft.com/en-us/rest/api/storageservices/create-user-delegation-sas:
const sasStart = new Date().toISOString();
const sasExpiry = new Date(new Date().getTime() + 20 * 60 * 1000).toISOString();
btnSAS.addEventListener("click",() => {
const StringToSign =
"rl" + // signedPermissions
"\n" +
sasStart + // signedStart
"\n" +
sasExpiry + // signedExpiry
"\n" +
"/blob/MYACCOUNT/MYCONTAINER" + // canonicalizedResource
"\n" +
sasKeyOID + // signedKeyObjectId
"\n" +
sasKeyTID + // signedKeyTenantId
"\n" +
sasKeyStart + // signedKeyStart
"\n" +
sasKeyExpiry + // signedKeyExpiry
"\n" +
sasKeyService + // signedKeyService
"\n" +
sasKeyVersion + // signedKeyVersion
"\n" +
"" + // signedAuthorizedUserObjectId
"\n" +
"" + // signedUnauthorizedUserObjectId
"\n" +
"16ca0b63-869e-4d76-8bf7-f859dcf02070" + // signedCorrelationId
"\n" +
"" + // signedIP
"\n" +
"https,http" + // signedProtocol
"\n" +
sasKeyVersion + // signedVersion
"\n" +
"c" + // signedResource
"\n" +
"" + // signedSnapshotTime
"\n" +
"" + // rscc
"\n" +
"" + // rscd
"\n" +
"" + // rsce
"\n" +
"" + // rscl
"\n" +
""; // rsct;
根据 msdn,在构建我们的 StringToSign 之后,我们需要生成“HMAC-SHA256(URL.Decode(UTF8.Encode(StringToSign)))”。我希望文档为您提供示例输入和输出,以便您在被迫创建函数时可以验证它。
这是我整理的 HMAC 函数:
async function myHMAC(base64Key,plainTextMessage) {
const decodedFromB64Key = atob(base64Key);
const cryptoKeyObj = await crypto.subtle.importKey(
"raw",new TextEncoder().encode(decodedFromB64Key),// convert key to ArrayBuffer
{ name: "HMAC",hash: "SHA-256" },// HmacImportParams obj
true,// extractable
["sign","verify"]
);
// message to sign must be URL.Decode(UTF8.Encode(StringToSign))
// but doing these things makes no difference to returned value so unused
const utf8StringToSign = unescape(encodeURIComponent(plainTextMessage));
const urlDecodedUft8StringToSign = decodeURIComponent(utf8StringToSign);
const messageArrayBuffer = new TextEncoder().encode(
plainTextMessage
);
const signature = await crypto.subtle.sign(
"HMAC",cryptoKeyObj,messageArrayBuffer
);
// return base64(signature)
return btoa(String.fromCharCode(...new Uint8Array(signature)));
}
像这样获取 HMAC:
const signedString = await myHMAC(sasKeyValue,StringToSign);
并最终将该值后缀到此 URL 并在 Postman 中使用:
console.log(
"https://MYACCOUNT.blob.core.windows.net/MYCONTAINER" +
"?restype=container" +
"&comp=list" +
"&sp=rl" +
"&st=" +
sasStart +
"&se=" +
sasExpiry +
"&spr=https,http" +
"&sv=" +
sasKeyVersion +
"&sr=c" +
"&sig="
);
回复:
<?xml version="1.0" encoding="utf-8"?>
<Error>
<Code>AuthenticationFailed</Code>
<Message>Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature.
RequestId: X
Time:Y</Message>
<AuthenticationErrorDetail>Signature fields not well formed.</AuthenticationErrorDetail>
</Error>
正如所建议的那样,这些字段存在一些问题。 StringToSign 中的字段是否应该与作为查询参数传递给 Blob REST API 的字段完全匹配?我确定我已经读过 StringToSign 必须为任何未使用的可选参数包含一个空字符串 - 不确定如何将其作为 GET 请求查询参数处理。 [1]:https://gauravmantri.com/2020/02/21/avoiding-authorizationfailed-error-when-hand-crafting-shared-access-signature-for-azure-storage/#disqus_thread
解决方法
试试这个代码来创建 SAS 令牌:
StringToSign = 'xxxxxxx';
let sig = crypto.createHmac('sha256',Buffer.from(key,'base64')).update(StringToSign,'utf8').digest('base64');
let sasToken = `sv=${(signedversion)}&ss=${(signedservice)}&srt=${(signedresourcetype)}&sp=${(signedpermissions)}&se=${encodeURIComponent(signedexpiry)}&spr=${(signedProtocol)}&sig=${encodeURIComponent(sig)}`;
console.log(sasToken)
如果你想使用 SDK(@azure/storage-blob),你可以使用 generateBlobSASQueryParameters 方法。
// Generate user delegation SAS for a container
const userDelegationKey = await blobServiceClient.getUserDelegationKey(startsOn,expiresOn);
const containerSAS = generateBlobSASQueryParameters({
containerName,// Required
permissions: ContainerSASPermissions.parse("racwdl"),// Required
startsOn,// Optional. Date type
expiresOn,// Required. Date type
ipRange: { start: "0.0.0.0",end: "255.255.255.255" },// Optional
protocol: SASProtocol.HttpsAndHttp,// Optional
version: "2018-11-09" // Must greater than or equal to 2018-11-09 to generate user delegation SAS
},userDelegationKey,// UserDelegationKey
accountName
).toString();
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。