我一直在尝试创建一个网页以允许我:
- 使用 msal-browser 库生成 Windows 身份平台登录弹出窗口以获取不记名令牌。
- 使用它从 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