Azure Service Bus 中的身份驗證方式 Shared Access Signature
提示
本文更新時間:2016年01月01日.
今天踩了一個坑……
在 Azure 的 Service Bus (服務總線) 中,每個請求是需要保護一個驗證信息的。 這個驗證信息可以是SAS,也可以是ACS(已經不建議使用)。
注解
如果是可以安全保存Key的服務器想要訪問 Service Bus,并且不是使用.NET Core,是不需要知道有SAS Token 這種東西存在的。
小技巧
Azure Storage 對于非public的文件方法也是使用這個的思想,但用于簽名的具體參數不同。
Shared Access Signature 機制?
Shared Access Signature ,從名字上不難看出,這是一個通過簽名來共享訪問權限的機制。 在 Service Bus 中,我們有一個名為 RootManageSharedAccessKey 的Key, 這個 Key 擁有這個 Service Bus 的完整訪問權限。 我們可以為每個隊列/主題/事件中心 創建獨立的Key,甚至可以為他們分配不同的權限。
注解
在管理門戶中看到的 Key 一般有兩個, PrimaryKey 和 SecondaryKey ,任意一個都可用。 兩個的目的是方便定期更換密鑰,建議使用 PrimaryKey 。
由于 Service Bus 的發送方可能是終端設備,比如IoT設備等,就這樣把Key下發下去很不安全,因此可以 Shared Access Signature 機制。
當我們需要訪問某個資源,如 https://qinnz.servicebus.windows.net/myeventhub/message 時, 我們用剛剛的key對這個資源和可訪問的有效期進行簽名,并把簽名+資源+有效期三項內容發給服務器,服務器即可進行簽名驗證。
Shared Access Signature 生成過程?
這里,我們就用 RootManageSharedAccessKey 密鑰進行簽名。
- 對資源Uri進行encode,資源Uri可以是請求的資源或者它的父資源。
- 得到過期時間的Unix時間戳(Azure Storage 使用的是人可識別的日期格式)
- 將Key使用 UTF-8 編碼轉換為字節數組,作為簽名密鑰
- 對資源uri和時間戳進行簽名(使用 HMAC-SHA256 算法)
- 生成簽名字符串
public class SasToken { public static string Generator(string uri, DateTimeOffset expiryTime, string keyName, string keyValue) { string uriEncoded = Uri.EscapeDataString(uri.ToString()); long expiry = expiryTime.ToUnixTimeSeconds(); //Only available on .Net 4.6 byte[] key = Encoding.UTF8.GetBytes(keyValue); var hmac = new HMACSHA256(key); string stringToSign = uriEncoded + "\n" + expiry.ToString(); string signature = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign))); Debug.WriteLine(stringToSign); return $"SharedAccessSignature sr={uriEncoded}&sig={Uri.EscapeDataString(signature)}&se={expiry}&skn={keyName}"; } } |
嗯,上面就是我自己實現的……坑就在于第7行……
在微軟官方文檔中,他們也以為是 base64 編碼的,我剛剛在Github上提交了這個 issue
事實上,微軟官方是有C#類庫來做這件事的(下面第15行); 我自己實現僅為了能在別的平臺上能用。 下面說說官方給出的實現代碼和我的代碼做比較,注意替換自己的key和keyName:
using System; using Microsoft.ServiceBus; namespace SharedAccessSignatureTokenGenerator { class Program { private static string resource = "<your-resource>"; private static string key = "<your-key-for-RootManageSharedAccessKey>"; private static string keyName = "RootManageSharedAccessKey"; static void Main(string[] args) { // Get Token using Microsoft.ServiceBus.SharedAccessSignatureTokenProvider.GetSharedAccessSignature var token = SharedAccessSignatureTokenProvider.GetSharedAccessSignature(keyName, key, resource, new TimeSpan(24, 0, 0)); //Get Token using my SasToken.Generator long se = long.Parse(token.Substring(token.IndexOf("se=") + 3, 10)); var mytoken = SasToken.Generator(resource, new DateTimeOffset(2050, 1, 1, 0, 0, 0, new TimeSpan()), keyName, key); Console.WriteLine(mytoken); Console.WriteLine(token); } } } |
注解
由于我使用的 Uri.EscapeDataString 返回的是大寫字母,造成簽名值不一樣;不過因為sr同樣不一樣,并沒有什么影響。
可以使用 uriEncoded=uriEncoded.ToLower(); 使得兩種方法簽名一致。
訪問測試?
現在,就可以向 Service Bus 發送消息了。
注解
Event Hub 的API可以參考 這里
例如我們可以向 sb://qinnz.servicebus.windows.net/mail/messages 發送消息, 可以指定 sr 為 sb://qinnz.servicebus.windows.net/mail/messages 或 sb://qinnz.servicebus.windows.net (如果key的權限足夠,那么此時的SAS具有整個servicebus的訪問權限) 我們需要設定SAS的過期時間,已經你使用的密鑰的名字,使Azure可以在服務器端驗證簽名。
最終,我們生成下面的字符串作為HTTP請求的Authorization請求頭。
SharedAccessSignature sr=sb%3A%2F%2Fqinnz.servicebus.windows.net%2Fmail%2Fmessages &sig=Augn3gnz4PEz%2Faaaaaaaaaaaaaaaaaaaacr%2B4vd2tWE%3D &se=2000000000&skn=RootManageSharedAccessKey
下面我就用 Fiddler 模擬發送POST請求,內容如下(處于安全原因,我替換了簽名值):
POST https://qinnz.servicebus.windows.net/mail/messages Authorization: SharedAccessSignature sr=sb%3A%2F%2Fqinnz.servicebus.windows.net%2Fmail%2Fmessages&sig=Augn3gnz4PEz%2Faaaaaaaaaaaaaaaaaaaacr%2B4vd2tWE%3D&se=2000000000&skn=RootManageSharedAccessKey Content-Type: application/atom+xml;type=entry;charset=utf-8 Host: qin-nz.servicebus.windows.net Content-Length: 5 hello! |
來自: http://www.cnblogs.com/qin-nz/p/azure-service-bus-shared-access-signature-token.html