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. VR全景智慧城市--2017年VR项目加盟将是一个机遇

    全景智慧城市项目是河南艺境空间文化传播有限公司自主开发的国内第一家商业全景平台, 旨在构建全景城市,实现智慧生活,让人们随时随地身临其境拥有全世界,享受快捷.真实.趣味.优质生活. 以VR虚拟现实技术 ...

  3. 结构化CSS设计思维

    LESS.SASS等预处理器给CSS开发带来了语法的灵活和便利,其本身却没有给我们带来结构化设计思维.很少有人讨论CSS的架构设计,而很多框架本身,如Bootstrap确实有架构设计思维作为根基. 要 ...

  4. java实现文件批量导入导出实例(兼容xls,xlsx)

    1.介绍 java实现文件的导入导出数据库,目前在大部分系统中是比较常见的功能了,今天写个小demo来理解其原理,没接触过的同学也可以看看参考下. 目前我所接触过的导入导出技术主要有POI和iRepo ...

  5. sql备份(.mdf文件备份)

    第一步: 右键需要备份的数据库(这里以MyDB为例),选择“属性”. 第二步: 选择“文件”,复制路径 第三步: 打开文件所在目录,复制MyDB.mdf和MyDB_log.ldf 第四步: 把数据库停 ...

  6. HashSet集合

    HashSet特点 1.无序,不允许重复(无序指元素顺序与添加顺序不一致,每次遍历出来的位置不是恒久不变的) 2.HashSet通过调用hashCode()和equals方法来剔除重复 3.HashS ...

  7. 让Chrome看不了WWDC直播的HLS技术详解

    Requirements: Live streaming uses Apple's HTTP Live Streaming (HLS) technology. HLS requires an iPho ...

  8. 动态添加Redis密码认证

    如果redis已在线上业务使用中,但没有添加密码认证,那么如何在不影响业务服务的前提下给redis添加密码认证,就是一个需要仔细考虑的问题. 本文描述一种可行的方案,适用于客户端使用了jedis连接池 ...

  9. ssh代理上网

    背景: 公司开发机没有外网,但可以通过ssh连接到另一台可以上公网的机器,所以想通过ssh代理的方式上网,简单又方便,而且需要的时候上,不需要的时候也可以不上 配置: 超级简单 在开发机上建立ssh隧 ...

  10. canvas水波纹效果

    先看效果 演示效果 自然界中水波纹效果十分麻烦,我这里只是根据水波纹的几个特性,在理想环境下模拟水波纹的扩散效果. 这里应用到的属性有:扩散.波动.折射. 扩散:很好理解,水波纹会从触发原点开始向周围 ...