zoukankan      html  css  js  c++  java
  • 如何生成Azure SAS Token

    Azure PaaS服务密钥的安全性文章中,提到过客户端实际上发送的是Token,而不是密钥。那么Token是该如何生成呢?

    Azure相应服务的SDK其实都提供了或者内置了生成Token的方法,可以直接调用,但是如果是想通过REST API的方式访问,而不像依赖于SDK,那么就需要自行生成Token了。其实Token本质上就是一个有一定规则的字符串,所以实现起来也不难。

    一般情况下,Token的格式是这样子的

    SharedAccessSignature sig={signature-string}&se={expiry}&skn={policyName}&sr={URL-encoded-resourceURI}

    其中各个参数的期望的值是:

    • signature-string:基于密钥使用HMAC-SHA256算法加密字符串“{URL-encoded-resourceURI} + " " + expiry”,然后转换成base64并URL编码
    • expiry:从1970-01-01 00:00:00算起到你期望过期时间的总秒数,并转换UTF8字符串
    • policyName:密钥对应的共享访问规则名称
    • URL-encoded-resourceURI:URL编码的小写的目标访问资源URL字符串

    有一个比较坑的地方要注意下,Service Bus和IoT Hub令牌生成的格式虽然看上去一样,但在生成signature-string时有细微的区别:

    • Service Bus:直接获取密钥的byte数组 
      var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(key))
    • IoT Hub:按Base64字符串解码
      var hmac = new HMACSHA256(Convert.FromBase64String(key));
      

     完整示例代码如下:

    using System;
    using System.Web;
    using System.Text;
    using System.Security.Cryptography;
    using System.Globalization;
    
    static string createToken(string resourceUri, string keyName, string key)
    {
        var encodedResourceUri = HttpUtility.UrlEncode(resourceUri);
    
        var sinceEpoch = DateTime.UtcNow - new DateTime(1970, 1, 1);
        var week = 60 * 60 * 24 * 7;
        var expiry = Convert.ToString((int)sinceEpoch.TotalSeconds + week);
    
        var stringToSign = encodedResourceUri + "
    " + expiry;
        var stringToSignInBytes = Encoding.UTF8.GetBytes(stringToSign);
        // For Service Bus
        var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(key));
        // For IoT Hub
        // var hmac = new HMACSHA256(Convert.FromBase64String(key));
        var signature = Convert.ToBase64String(hmac.ComputeHash(stringToSignInBytes));
        var encodedSignature = HttpUtility.UrlEncode(signature);
    
        var sasToken = String.Format(CultureInfo.InvariantCulture, 
            "SharedAccessSignature sr={0}&sig={1}&se={2}&skn={3}",
            encodedResourceUri,
            encodedSignature, 
            expiry, 
            keyName);
        return sasToken;
    }
    View Code

    而Storage则要复杂得多,而且有两种格式。一种是用做Authorization头部的SharedKey格式,另一种则是可放置在URL中的SAS格式

    SharedKey格式

    格式为

    SharedKey {AccountName}:{Signature}

    而且Table和其他几种存储服务的签名字符串格式还不一样。

    Blob,Queue和File

    StringToSign = VERB + " " +
             Content-Encoding + " " +
               Content-Language + " " +
            Content-Length + " " +
            Content-MD5 + " " +
            Content-Type + " " +
                            Date + " " +
                            If-Modified-Since + " " +
                            If-Match + " " +
                            If-None-Match + " " +
                            If-Unmodified-Since + " " +
                            Range + " " +
                            CanonicalizedHeaders +
                            CanonicalizedResource;

    Table

    StringToSign = VERB + " " +
                            Content-MD5 + " " +
                            Content-Type + " " +
                            Date + " " +
                            CanonicalizedResource;

    具体示例代码如下

    构建标准化的头部字符串
    public string GetCanonicalizedHeaders(HttpWebRequest request)
    {
        ArrayList headerNameList = new ArrayList();
        StringBuilder sb = new StringBuilder();
        foreach (string headerName in request.Headers.Keys)
        {
            if (headerName.ToLowerInvariant().StartsWith("x-ms-", StringComparison.Ordinal))
            {
                headerNameList.Add(headerName.ToLowerInvariant());
            }
        }
        headerNameList.Sort();
        foreach (string headerName in headerNameList)
        {
            StringBuilder builder = new StringBuilder(headerName);
            string separator = ":";
            foreach (string headerValue in GetHeaderValues(request.Headers, headerName))
            {
                string trimmedValue = headerValue.Replace("
    ", String.Empty);
                builder.Append(separator);
                builder.Append(trimmedValue);
                separator = ",";
            }
            sb.Append(builder.ToString());
            sb.Append("
    ");
        }
        return sb.ToString();
    }
    
    private ArrayList GetHeaderValues(NameValueCollection headers, string headerName)
    {
        ArrayList list = new ArrayList();
        string[] values = headers.GetValues(headerName);
        if (values != null)
        {
            foreach (string str in values)
            {
                list.Add(str.TrimStart(null));
            }
        }
        return list;
    }
    View Code
    构建标准化的资源字符串
    public string GetCanonicalizedResource(Uri address, string accountName, bool isTableStorage = false)
    {
        StringBuilder canonicalizedResourceStrBuilder = new StringBuilder();
        StringBuilder builder = new StringBuilder("/");
        builder.Append(accountName);
        builder.Append(address.AbsolutePath);
        canonicalizedResourceStrBuilder.Append(builder.ToString());
        NameValueCollection values2 = new NameValueCollection();
        if (!isTableStorage)
        {
            NameValueCollection values = HttpUtility.ParseQueryString(address.Query);
            foreach (string str2 in values.Keys)
            {
                ArrayList list = new ArrayList(values.GetValues(str2));
                list.Sort();
                StringBuilder builder2 = new StringBuilder();
                foreach (object obj2 in list)
                {
                    if (builder2.Length > 0)
                    {
                        builder2.Append(",");
                    }
                    builder2.Append(obj2.ToString());
                }
                values2.Add((str2 == null) ? str2 : str2.ToLowerInvariant(), builder2.ToString());
            }
        }
        ArrayList list2 = new ArrayList(values2.AllKeys);
        list2.Sort();
        foreach (string str3 in list2)
        {
            StringBuilder builder3 = new StringBuilder(string.Empty);
            builder3.Append(str3);
            builder3.Append(":");
            builder3.Append(values2[str3]);
            canonicalizedResourceStrBuilder.Append("
    ");
            canonicalizedResourceStrBuilder.Append(builder3.ToString());
        }
        return canonicalizedResourceStrBuilder.ToString();
    }
    View Code
    获取SharedKey类型认证Token

    这里要注意的是,当发送的请求中使用了哪些头部,那么这里也有设置相应头部的值。比如一个putblob请求,包含了MD5头部值,那么这里生成token,也需要传入相应的MD5值,否则会验证失败。

    public string GetAuthorizationToken(
        string storageAccountName,
        string requestUri, 
        string method,
        DateTime date,
        string contentEncoding = "",
        string contentLanguage = "",
        int contentLength = 0,
        string contentMd5 = "",
        string contentType = "",            
        string ifModifiedSince = "",
        string ifMatch = "",
        string ifNoneMatch = "",
        string ifUnmodifiedSince = "",
        string range = "",
        bool isTableStorage = false)
    {            
        HttpWebRequest request = HttpWebRequest.Create(requestUri) as HttpWebRequest;
        request.Headers.Add("x-ms-date", date.ToString("R", System.Globalization.CultureInfo.InvariantCulture));
        request.Headers.Add("x-ms-version", "2017-04-17");
    
        string messageSignature;
    
        if (isTableStorage)
        {
            messageSignature = String.Format("{0}
    {1}
    {2}
    {3}
    {4}",
                method,
                contentMd5,
                contentType,
                date.ToString("R", System.Globalization.CultureInfo.InvariantCulture),
                GetCanonicalizedResource(request.RequestUri, storageAccountName)
                );
        }
        else
        {
            string canonicalizedHeaders = GetCanonicalizedHeaders(request);
            string canonicalizedResource = GetCanonicalizedResource(request.RequestUri, storageAccountName);
            messageSignature = String.Format("{0}
    {1}
    {2}
    {3}
    {4}
    {5}
    {6}
    {6}
    {7}
    {8}
    {9}
    {10}
    {11}
    {12}{13}",
                method,
                contentEncoding,
                contentLanguage,
                contentLength,
                contentMd5,
                contentType,
                "", // included in the header, so empty here
                ifModifiedSince,
                ifMatch,
                ifNoneMatch,
                ifUnmodifiedSince,
                range,
                canonicalizedHeaders,
                canonicalizedResource
                );
        }
        var signatureBytes = Encoding.UTF8.GetBytes(messageSignature);
        var hmac = new HMACSHA256(Convert.FromBase64String(_storageKey));
        var authorizationHeader = "SharedKey " + storageAccountName + ":" + Convert.ToBase64String(hmac.ComputeHash(signatureBytes));
        return authorizationHeader;
    }
    View Code

    SAS格式

    这种SAS token可限制的更多,不止是过期时间,还包括了可允许访问的IP地址以及更具体的权限。Azure管理门户上提供了直接生成此类token的功能,如下图所示:

    因为这种token对于不同访问级别(服务级别和账户级别),以及不同的存储服务(Blob,File,Queue和Table)有不同的格式,我后面将单独写一篇文章来介绍。

    参考文章:

  • 相关阅读:
    设计模式
    DOS批处理脚本
    BAT 批处理脚本 教程
    spring4配置文件详解
    软件过程
    error C2440 “static_cast” 无法从“void (__thiscall C* )(void)...
    error C2065: “IDD_DIALOG1”: 未声明的标识符
    在另一个编辑器中打开
    Github 结合 Hexo 搭建轻量博客
    收藏---wordpress搭建出来的blog
  • 原文地址:https://www.cnblogs.com/hula100/p/7202705.html
Copyright © 2011-2022 走看看