在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. Python爬虫学习(二) ——————爬取前程无忧招聘信息并写入excel

    作为一名Pythoner,相信大家对Python的就业前景或多或少会有一些关注.索性我们就写一个爬虫去获取一些我们需要的信息,今天我们要爬取的是前程无忧!说干就干!进入到前程无忧的官网,输入关键字&q ...

  2. 点评阿里JAVA手册之编程规约(命名风格、常量定义、代码风格、控制语句、注释规约)

    下载原版阿里JAVA开发手册  [阿里巴巴Java开发手册v1.2.0] 本文主要是对照阿里开发手册,注释自己在工作中运用情况. 本文难度系数为一星(★) 码出高效.码出质量. 代码的字里行间流淌的是 ...

  3. intersect for multiple vectors in R

    Say you have a <- c(1,3,5,7,9) b <- c(3,6,8,9,10) c <- c(2,3,4,5,7,9) A straightforward way ...

  4. 【JAVAEE学习笔记】hibernate01:简介、搭建、配置文件详解、API详解和CRM练习:保存客户

    今日学习:hibernate是什么 一.hibernate是什么 框架是什么: 1.框架是用来提高开发效率的 2.封装了好了一些功能.我们需要使用这些功能时,调用即可.不需要再手动实现. 3.所以框架 ...

  5. struts2+hibernate+spring配置版框架搭建以及简单测试(方便脑补)

    为了之后学习的日子里加深对框架的理解和使用,这里将搭建步奏简单写一下,目的主要是方便以后自己回来脑补: 1:File--->New--->Other--->Maven--->M ...

  6. 利用浏览器查找font-family的css编码

    提供一种利用Chrome快速查找字体编码的小技巧 打开浏览器,按下键盘F12 点击Console控制台 输入escape("要查询的字体中文名称")(注意:括号与引号都是英文输入法 ...

  7. 去除MyEclipse频繁弹出的Update Progress窗口

    方法1: 1.关闭updating index Window => Preferences => Myeclipse Enterprise Workbench => Maven4My ...

  8. 如何使用 ui-router-extras

    为了使用ui-router创建tabs构架,使用ui-router-extras 使用方法: 0. 安装包 bower install ui-router-extras --save-dev 1. 引 ...

  9. Qlik报表开发见解

    因为项目需要,最近去做了Qlik Sense报表开发,学习了Qlik报表的开发方法和一些基础的开发模式,以下是我对Qlik报表开发的一些见解,个人水平有限,欢迎大神指导. 1.Qlik Sense的函 ...

  10. C. Karen and Game

    C. Karen and Game time limit per test 2 seconds memory limit per test 512 megabytes input standard i ...