通过浏览器生成 Azure blob 用户委派 SAS 的问题 javascript
Issue generating Azure blob user delegation SAS via browser javascript
我一直在尝试创建一个网页以允许我:
- 使用 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
尝试使用此代码创建 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();
我一直在尝试创建一个网页以允许我:
- 使用 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
尝试使用此代码创建 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();