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} + "\n" + 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(, , );
var week = * * * ;
var expiry = Convert.ToString((int)sinceEpoch.TotalSeconds + week); var stringToSign = encodedResourceUri + "\n" + 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;
}

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

SharedKey格式

格式为

SharedKey {AccountName}:{Signature}

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

Blob,Queue和File

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

Table

StringToSign = VERB + "\n" +
                        Content-MD5 + "\n" +
                        Content-Type + "\n" +
                        Date + "\n" +
                        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("\r\n", String.Empty);
builder.Append(separator);
builder.Append(trimmedValue);
separator = ",";
}
sb.Append(builder.ToString());
sb.Append("\n");
}
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;
}
构建标准化的资源字符串
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 > )
{
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("\n");
canonicalizedResourceStrBuilder.Append(builder3.ToString());
}
return canonicalizedResourceStrBuilder.ToString();
}
获取SharedKey类型认证Token

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

public string GetAuthorizationToken(
string storageAccountName,
string requestUri,
string method,
DateTime date,
string contentEncoding = "",
string contentLanguage = "",
int contentLength = ,
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}\n{1}\n{2}\n{3}\n{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}\n{1}\n{2}\n{3}\n{4}\n{5}\n{6}\n{6}\n{7}\n{8}\n{9}\n{10}\n{11}\n{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;
}

SAS格式

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

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

参考文章:

如何生成Azure SAS Token的更多相关文章

  1. 使用PowerShell创建Azure Storage的SAS Token访问Azure Blob文件

    Azure的存储包含Storage Account.Container.Blob等具体的关系如下: 我们常用的blob存储,存放在Storage Account的Container里面. 目前有三种方 ...

  2. jwt认证生成后的token如何传回后端并解析的详解

    jwt认证生成后的token后端解析 一.首先前端发送token token所在的位置headers {'authorization':token的值',Content-Type':applicati ...

  3. Azure China (10) 使用Azure China SAS Token

    <Windows Azure Platform 系列文章目录> 本文介绍的是国内由世纪互联运维的Azure China 注意:本文介绍的是Azure China Storage Priva ...

  4. springcloud +spring security多种验证方式之第三方token生成自己的token通过校验和自己的表单验证大体流程

    步骤: 1.继承 WebSecurityConfigurerAdapter.class,其中使用两个过滤器,一个spring scurity自带的UsernamePasswordAuthenticat ...

  5. jwt认证生成后的token后端解析

    一.首先前端发送token token所在的位置headers {'authorization':token的值',Content-Type':application/json} 在ajax写 //只 ...

  6. token是个什么东西?怎样生成并携带token

    什么是token及怎样生成token  转载自:https://www.cnblogs.com/lufeiludaima/p/pz20190203.html 什么是token Token是服务端生成的 ...

  7. ECDSA密钥对生成以及在Token中的应用

    1 概述 本文主要讲述了如何利用Openssl生成ECDSA密钥对,并利用Auth0库进行Token生成及验证的过程. 2 ECDSA 2.1 简介 ECC(Elliptic Curve Crypto ...

  8. 生成一个唯一token

    $token = md5(uniqid(rand(), true));

  9. 根据UUID和MD5, 生成可以用作Token的字符串

    import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util. ...

随机推荐

  1. 将Java Web项目部署到远程主机上

    这里讲的是Java Web项目 第一步:购买主机,如果是大学生可以购买学生机,一个月9.9元,阿里云ECS服务器,自己选择不同的操作系统和镜像 ,我的选择 得到用户名和密码,可以进行ssh远程登录,登 ...

  2. SQL SERVER大话存储结构(6)_数据库数据文件

            数据库文件有两大类:数据文件跟日志文件,每一个数据库至少各有一个数据文件或者日志文件,数据文件用来存储数据,日志文件用来存储数据库的事务修改情况,可用于恢复数据库使用.     这里分 ...

  3. 一键帮你复制多个文件到多个机器——PowerShell小脚本(内附PS远程执行命令问题解析)

    作为一个后台程序猿,经常需要把一堆程序集(DLL)或者应用程序(EXE)复制到多个服务器上,实现程序的代码逻辑更新,用以测试新的功能或改动逻辑.这里给大家介绍一个自己实现的PowerShell脚本,方 ...

  4. HTMLCollection 对象详解,以及为什么循环获取的dom合集操作可能会出现下标不正确的情况?

    有时候循环dom合集,然后操作其中的某些dom之后,发现下标不正确了 比如我们要删除一个dom合集的时候: var selectDom = document.getElementsByClassNam ...

  5. 如何动态加载js文件,$.getScript()方法的使用

    有时候我们需要动态在页面中加载js文件,jquery封装了getScript()方法,不用自己再创建标签了. 写法: $.getScript("name.js",function( ...

  6. java中static关键字的作用

    java中static关键字主要有两种作用: 第一:为某特定数据类型或对象分配单一的存储空间,而与创建对象的个数无关. 第二,实现某个方法或属性与类而不是对象关联在一起 简单来说,在Java语言中,s ...

  7. Log4j2分析与实践

    当前网络上关于Log4j2的中文文章比较零散,这里整理了一下关于Log4j2比较全面的一些文章,供广大技术人员参考 Log4j2分析与实践-认识Log4j2 Log4j2分析与实践-架构 Log4j2 ...

  8. CommonsChunkPlugin并不是分离第三方库的好办法(DllPlugin科学利用浏览器缓存)

    webpack算是个磨人的小妖精了.之前一直站在glup阵营,使用browserify打包,发现webpack已经火到爆炸,深怕被社区遗落,赶紧拿起来把玩一下.本来只想玩一下的.尝试打包了以后,就想启 ...

  9. 各种Web漏洞测试平台

    Sqli Lab​支持报错注入.二次注入.盲注.Update注入.Insert注入.Http头部注入.二次注入练习等.支持GET和POST两种方式. https://github.com/Audi-1 ...

  10. 用GAN生成二维样本的小例子

    同步自我的知乎专栏:https://zhuanlan.zhihu.com/p/27343585 本文完整代码地址:Generative Adversarial Networks (GANs) with ...