如何生成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} + "\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)有不同的格式,我后面将单独写一篇文章来介绍。
参考文章:
- https://docs.microsoft.com/en-us/rest/api/storageservices/Authentication-for-the-Azure-Storage-Services
- https://docs.microsoft.com/en-us/azure/storage/storage-dotnet-shared-access-signature-part-1
- https://docs.microsoft.com/en-us/azure/service-bus-messaging/service-bus-sas
- https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-devguide-security
如何生成Azure SAS Token的更多相关文章
- 使用PowerShell创建Azure Storage的SAS Token访问Azure Blob文件
Azure的存储包含Storage Account.Container.Blob等具体的关系如下: 我们常用的blob存储,存放在Storage Account的Container里面. 目前有三种方 ...
- jwt认证生成后的token如何传回后端并解析的详解
jwt认证生成后的token后端解析 一.首先前端发送token token所在的位置headers {'authorization':token的值',Content-Type':applicati ...
- Azure China (10) 使用Azure China SAS Token
<Windows Azure Platform 系列文章目录> 本文介绍的是国内由世纪互联运维的Azure China 注意:本文介绍的是Azure China Storage Priva ...
- springcloud +spring security多种验证方式之第三方token生成自己的token通过校验和自己的表单验证大体流程
步骤: 1.继承 WebSecurityConfigurerAdapter.class,其中使用两个过滤器,一个spring scurity自带的UsernamePasswordAuthenticat ...
- jwt认证生成后的token后端解析
一.首先前端发送token token所在的位置headers {'authorization':token的值',Content-Type':application/json} 在ajax写 //只 ...
- token是个什么东西?怎样生成并携带token
什么是token及怎样生成token 转载自:https://www.cnblogs.com/lufeiludaima/p/pz20190203.html 什么是token Token是服务端生成的 ...
- ECDSA密钥对生成以及在Token中的应用
1 概述 本文主要讲述了如何利用Openssl生成ECDSA密钥对,并利用Auth0库进行Token生成及验证的过程. 2 ECDSA 2.1 简介 ECC(Elliptic Curve Crypto ...
- 生成一个唯一token
$token = md5(uniqid(rand(), true));
- 根据UUID和MD5, 生成可以用作Token的字符串
import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util. ...
随机推荐
- php处理表单中的复选框问题以及js实现全选
做的一个项目中遇到了全选和取消全选的问题,这是一个很普遍的功能,,虽然我们经常用到,但是真正做起来却发现行不通,在网上找了些,大部分都是ie,但是谷歌内核浏览器不能正常实现,所以经过小小的调整,今天就 ...
- (转)Java 读写Properties配置文件
原文:http://www.cnblogs.com/xudong-bupt/p/3758136.html 1.Properties类与Properties配置文件 Properties类继承自Hash ...
- 简单两步快速学会使用Mybatis-Generator自动生成entity实体、dao接口和简单mapper映射(用mysql和oracle举例)
前言: mybatis-generator是根据配置文件中我们配置的数据库连接参数自动连接到数据库并根据对应的数据库表自动的生成与之对应mapper映射(比如增删改查,选择性增删改查等等简单语句)文件 ...
- LANMP一键安装包 版本服务任你选 可安装单一服务
介绍与使用 更多内容请到 乌龟运维 wuguiyunwei.com 请保证在系统原有yum源文件存在的情况下运行此脚本 以下以centos7.3为例: 下面以安装LNMP为例: ? 1 wget ht ...
- [Linux] PHP程序员玩转Linux系列-腾讯云硬盘扩容挂载
1.PHP程序员玩转Linux系列-怎么安装使用CentOS 2.PHP程序员玩转Linux系列-lnmp环境的搭建 3.PHP程序员玩转Linux系列-搭建FTP代码开发环境 4.PHP程序员玩转L ...
- mac+phpstorm增加xdebug调试
一.版本信息 mac 10.10.5 phpstorm 10.0.3 xdebug 版本需要与phpstorm匹配,匹配地址 点我匹配 点我查看所有版本 提示:不确定xdebug版本的,把php ...
- 从Owin到System.Web.Http.Owin的HttpMessageHandlerAdapter看适配器模式
.mytitle { background: #2B6695; color: white; font-family: "微软雅黑", "宋体", "黑 ...
- php根据经纬度获取城市名
/*php根据经纬度获取城市名*/ function get_my_addr_infos(){ $ch = curl_init(); $timeout = 5; $lat = $list['info' ...
- NoSQL数据库:Redis适用场景及产品定位
传统MySQL+ Memcached架构遇到的问题 实际MySQL是适合进行海量数据存储的,通过Memcached将热点数据加载到cache,加速访问,很多公司都曾经使用过这样的架构,但随着业务数据量 ...
- 【LeetCode】73. Set Matrix Zeroes
题目: Given a m x n matrix, if an element is 0, set its entire row and column to 0. Do it in place. Fo ...