tl;dr
SAS トークンの構造を学習するために、ライブラリを使わないで、Node.js で生成しました。
( Java 版は こちら )
公式ドキュメント : サービス SAS を作成する
コード
BLOB オブジェクト を 30秒 だけ Read 可能なURL
main.js
const SAS = require("./SAS");
const name = "ストレージアカウント名";
const key = "キー";
const container = "コンテナ名";
const blob = "ブロブ名";
const expiry = 30; // 30 秒間有効
const filename = "ファイル名";
const sas = new SAS(name, key);
const url = sas.getUrl(container, blob, expiry, filename);
console.log(url);
SAS.js
const crypto = require("crypto");
class SAS {
// コンストラクタ
// @param name ストレージアカウント名
// @param key ストレージアカウントキー
constructor(name, key) {
this.account_name = name;
this.account_key = key;
}
// トークン付きURLを生成する
// @param container コンテナ名
// @param blob ブロブ名
// @param expiry_second トークン有効期間
// @param filename ファイル名
getUrl(container, blob, expiry_second, filename) {
const token = this.getToken(container, blob, expiry_second, filename);
const url = `https://${this.account_name}.blob.core.windows.net/${container}/${blob}`;
return `${url}?${token}`;
}
// トークンを生成する
// @param container コンテナ名
// @param blob ブロブ名
// @param expiry_second トークン有効期間
// @param filename ファイル名
getToken(container, blob, expiry_second, filename) {
// パラメータ
const now = new Date();
const start = ""; // this.isoDate(now, start_second);
const expiry = this.isoDate(now, expiry_second);
const version = "2018-11-09";
const resource = "b";
const permission = "r";
const ip = ""; // "0.0.0.0/0";
const protocol = ""; // "https";
const canonicalizedResource = `/blob/${this.account_name}/${container}/${blob}`;
const rscc = "";
const rscd = `attachment; filename="${filename}"`;
const rsce = "";
const rscl = "";
const rsct = "";
const identifier = "";
const snapshot = "";
// シグネチャ (順番は厳守)
const stringToSign = [
permission,
start,
expiry,
canonicalizedResource,
identifier,
ip,
protocol,
version,
resource,
snapshot,
rscc, // Cache-Control
rscd, // Content-Disposition
rsce, // Content-Encoding
rscl, // Content-Language
rsct, // Content-Type
].join("\n");
// HMAC256
const signature = this.getHMAC256(stringToSign);
// クエリパラメータ (順番は任意)
const sas = [
["sp", permission], //
["sr", resource],
// ["st", start],
["se", expiry],
// ["sip", ip],
// ["spr", protocol],
["sv", version],
["sig", signature],
["rscd", rscd],
]
.map(([k, v]) => `${k}=${encodeURIComponent(v)}`)
.join("&");
return sas;
}
isoDate(now, second = 0) {
const date = new Date(now.getTime() + second * 1000);
return date.toISOString().substring(0, 19) + "Z";
}
getHMAC256(input) {
// 1. キー は Buffer (Base64 で デコード)
const key = Buffer.from(this.account_key, "base64");
const hmac = crypto.createHmac("sha256", key);
// 2. 入力 は Buffer (UTF8 で デコード)
const inp = Buffer.from(input, "utf8");
// 3. 出力 は Buffer を Base64 で エンコード
const out = hmac.update(inp).digest();
return out.toString("base64");
}
// 上を簡単に書き直すと
getHMAC256_(input) {
const key = Buffer.from(this.account_key, "base64");
return crypto.createHmac("sha256", key).update(input).digest("base64");
}
}
module.exports = SAS;
01/29 追記
ちなみに、az コマンドで同じことをやるには、こんな感じ
ACCOUNT_NAME='ストレージアカウント名'
ACCOUNT_KEY='キー'
FUTURE_DATE=$(date -v+30M '+%Y-%m-%dT%H:%MZ')
az storage account generate-sas \
--permissions r \
--resource-types o \
--services b \
--expiry $FUTURE_DATE \
--account-name $ACCOUNT_NAME \
--account-key $ACCOUNT_KEY