本文讨论共享访问签名(SAS)、工作原理,以及如何通过与 Azure 服务总线以与平台无关的方式使用它们。
SAS 基于在命名空间或消息传送实体(队列或主题)中配置的授权规则保护对服务总线的访问。 授权规则具有与特定权限关联的名称,并包含一个加密密钥对。 可以通过服务总线 SDK 或在自己的代码中使用规则的名称和密钥来生成 SAS 令牌。 然后,客户端可将令牌传递给服务总线,以证明请求的操作获得授权。
首选授权方法
Azure 服务总线支持使用 Microsoft Entra ID 授权访问服务总线命名空间及其实体。 使用 Microsoft Entra ID 返回的 OAuth 2.0 令牌来授权用户或应用程序提供优于共享访问签名的安全性和易用性。 SAS 密钥缺乏精细的访问控制,难以管理和轮换,并且没有审核功能将其使用与特定用户或服务主体相关联。
出于这些原因,我们建议尽可能将 Microsoft Entra ID 与 Azure 服务总线应用程序配合使用。 有关详细信息,请参阅以下文章:
- 使用 Microsoft Entra ID 对应用程序进行身份验证和授权以访问 Azure 服务总线实体
- 使用 Microsoft Entra ID 对托管标识进行身份验证以访问 Azure 服务总线资源
可以为服务总线命名空间禁用本地或 SAS 密钥身份验证,仅允许 Microsoft Entra 身份验证。 有关分步说明,请参阅禁用本地身份验证。
SAS 概述
共享访问签名是使用简单令牌的基于声明的授权机制。 使用 SAS 时,永远不会在网络上传递密钥。 密钥用于对服务稍后可以验证的信息进行加密签名。
可以使用类似于用户名和密码方案的 SAS,其中客户端立即拥有授权规则名称和匹配密钥。 还可以以类似于联合安全模型的方式使用 SAS,其中客户端从安全令牌服务接收受时间限制且已签名的访问令牌,而无需拥有签名密钥。
服务总线中的 SAS 身份验证配置了具有关联访问权限的命名 共享访问授权策略 ,以及一对主加密密钥和辅助加密密钥。 键是 Base64 表示形式的 256 位值。 在服务总线的队列和主题上,可以在命名空间级别配置规则。
注意
这些键是使用 Base64 表示形式的纯文本字符串。 在使用它们之前,不得对其进行解码。
SAS 令牌包含:
- 所选授权策略的名称。
- 要访问的资源的 URI。
- 过期时刻
- 通过所选授权规则的主或辅助加密密钥通过这些字段计算的 HMAC-SHA256 加密签名。
共享访问授权策略
每个服务总线命名空间和每个服务总线实体都有一个由规则组成的共享访问授权策略。 命名空间级别的策略应用到该命名空间中的所有实体,不管这些实体各自的策略配置如何。
对于每个授权策略规则,你需要确定三项信息:
名称。 作用域内的唯一名称。
范围。 有关资源的 URI。 服务总线命名空间的范围是完全限定的命名空间,比如
https://<yournamespace>.servicebus.chinacloudapi.cn/。权限。 策略规则授予的权限。 它们可以是以下两者的组合:
- 发送。 授予向实体发送消息的权限。
- 听。 授予接收消息(队列和订阅)和所有相关消息处理的权限。
- 管理。 授予管理命名空间拓扑的权限,包括创建和删除实体。 “管理”权限包括“发送和侦听”权限。
命名空间或实体策略最多可以容纳 12 个共享访问授权规则,为三组规则提供空间。 每组规则都包含基本权限,以及发送和侦听的组合。 此限制是每个实体,因此命名空间和每个实体最多可以有 12 个共享访问授权规则。
规则限制提醒我们,SAS 策略存储并不适合作为用户或服务帐户存储。 如果应用程序需要根据用户或服务标识授予服务总线的访问权限,应实现安全令牌服务,以便在执行身份验证和访问检查后颁发 SAS 令牌。
为授权规则分配 主密钥 和 辅助密钥。 这些密钥是加密强密钥,因此请务必保护它们。 它们始终在 Azure 门户中可用。
可以使用其中一个生成的密钥,并且随时可以重新生成密钥。 如果重新生成或更改策略中的密钥,以前基于该密钥颁发的所有令牌会立即失效。 但是,基于此类令牌的持续连接将继续工作,直到令牌过期。
创建服务总线命名空间时,会自动为命名空间创建一个名为 RootManageSharedAccessKey 的策略规则。 此策略具有整个命名空间的“管理”权限。 建议将此规则视为管理根帐户,不要在应用程序中使用它。 可以通过 Azure PowerShell 或 Azure CLI 在门户中命名空间的 “共享访问策略 ”选项卡上创建更多策略规则。
我们建议定期重新生成 SharedAccessAuthorizationRule 对象中使用的密钥。 提供了主要和辅助密钥槽,以便可以逐步轮换密钥。 如果应用程序通常使用主密钥,则可以将主密钥复制到辅助密钥槽中,然后仅重新生成主密钥。 然后,可以将新的主键值配置为客户端应用程序,这些客户端应用程序可通过辅助槽中的旧主密钥继续访问。 更新所有客户端后,可以重新生成辅助密钥,最终停用旧的主密钥。
如果知道或怀疑密钥遭到入侵,并且必须撤销密钥,则可以重新生成 SharedAccessAuthorizationRule 的 PrimaryKey 和 SecondaryKey 值,以将其替换为新密钥。 此过程将使得由旧密钥签名的所有令牌失效。
使用共享访问签名的最佳做法
在应用程序中使用共享访问签名时,需要知道以下两个可能的风险:
- 如果 SAS 泄露,则获得 SAS 的任何人都可以使用它。 此风险可能会对您的服务总线资源造成损害。
- 如果提供给客户端应用程序的 SAS 过期,并且应用程序无法从服务中检索新的 SAS,则过期可能会妨碍应用程序的功能。
下面这些针对使用共享访问签名的建议可帮助降低这些风险:
如有必要,让客户端自动续订 SAS。 客户端应在过期前续订 SAS,以便在提供 SAS 的服务不可用时允许重试时间。
如果 SAS 被设计用于若干预期在到期时间内完成的即时且短期操作,那么可能不需要续订,因为 SAS 不期望被续订。 但是,如果你有一个通过 SAS 定期发出请求的客户端,则过期的可能性将生效。
关键考虑因素是平衡 SAS 生存期较短的需求,同时需要确保客户端足够早地请求续订。 这种平衡有助于避免因 SAS 在成功续订之前过期而导致的中断。
请谨慎使用 SAS 开始时间。 如果将 SAS 的开始时间设置为 现在,可能会在前几分钟间歇性地看到故障。 原因是 时钟偏斜:当前时间因不同计算机而异。
通常,将开始时间至少设置为 15 分钟前。 或者根本不设置,这样在所有情况下都会立即生效。 相同的一般最佳做法也适用于到期时间。 请记住,在任何请求的任一方向,你可能观察到的时间偏差最多为 15 分钟。
特定于要访问的资源。 安全最佳做法是向用户提供所需的最低权限。 如果用户只需要对单个实体的读取访问权限,请向他们授予对该单个实体的读取访问权限,而不是对所有实体的读取/写入/删除访问权限。 如果 SAS 遭到入侵,则这种做法还有助于减少损坏,因为 SAS 在攻击者手中拥有更少的权力。
不要总是使用 SAS。 有时,与针对服务总线的特定操作相关的风险超过了 SAS 的优势。 对于此类操作,请创建一个在业务规则验证、身份验证和审核后写入服务总线的中间层服务。
始终使用 HTTPS 创建或分发 SAS。 如果 SAS 通过 HTTP 传递并截获,则执行中间人攻击的攻击者可以读取 SAS,然后像预期用户一样使用它。 这种情况可能会损害敏感数据,或者允许恶意用户损坏数据。
SAS 身份验证的配置
可以在服务总线命名空间、队列或主题上配置 SAS 策略。 目前不支持在服务总线订阅上配置它,但你可以使用在命名空间或主题上配置的规则来帮助保护对订阅的访问。
在以下示例中,manageRuleNS和sendRuleNSlistenRuleNS授权规则适用于队列 Q1 和主题 T1。 规则listenRuleQsendRuleQ仅适用于队列 Q1。 该 sendRuleT 规则仅适用于主题 T1。
生成 SAS 令牌
任何有权访问授权规则名称及其签名密钥的客户端都可以生成 SAS 令牌。 客户端通过使用以下格式创建字符串来生成令牌:
SharedAccessSignature sig=<signature-string>&se=<expiry>&skn=<keyName>&sr=<URL-encoded-resourceURI>
se:令牌到期时刻。 该整数反映令牌过期时自 1970 年 1 月 1 日(UNIX 纪元)以来的秒00:00:00 UTC数。skn:授权规则的名称。sr:所访问资源的 URL 编码 URI。sig:URL 编码 HMAC-SHA256 签名。 哈希计算类似于以下伪代码,并返回原始二进制输出的 Base64。urlencode(base64(hmacsha256(urlencode('https://<yournamespace>.servicebus.chinacloudapi.cn/') + "\n" + '<expiry instant>', '<signing key>')))
令牌包含非哈希值,以便收件人可以使用相同的参数重新计算哈希,并验证颁发者是否拥有有效的签名密钥。
资源 URI 是向其声明访问权限的服务总线资源的完整 URI。 例如: http://<namespace>.servicebus.chinacloudapi.cn/<entityPath> 或 sb://<namespace>.servicebus.chinacloudapi.cn/<entityPath>;,即 http://contoso.servicebus.chinacloudapi.cn/contosoTopics/T1/Subscriptions/S3。 URI 必须 进行百分比编码。
必须在此 URI 指定的实体或其分层父级之一上,配置签名用的共享访问授权规则。 在前面的示例中,实体为 http://contoso.servicebus.chinacloudapi.cn/contosoTopics/T1 或 http://contoso.servicebus.chinacloudapi.cn。
在<resourceURI>中,SAS 令牌对所有以signature-string为前缀的资源有效。
有关使用各种编程语言生成 SAS 令牌的示例,请参阅 “生成 SAS 令牌”。
重新生成密钥
建议定期重新生成共享访问授权策略中的密钥。 提供了主要和辅助密钥槽,以便可以逐步轮换密钥。 如果应用程序通常使用主密钥,则可以将主密钥复制到辅助密钥槽中,然后仅重新生成主密钥。 然后,可以将新的主键值配置为客户端应用程序,这些客户端应用程序可通过辅助槽中的旧主密钥继续访问。 更新所有客户端后,可以重新生成辅助密钥,最终停用旧的主密钥。
如果知道或怀疑密钥遭到入侵,并且必须撤销密钥,则可以重新生成共享访问授权策略的主密钥和辅助密钥,以将其替换为新密钥。 此过程将使得由旧密钥签名的所有令牌失效。
若要在 Azure 门户中重新生成主密钥和辅助密钥,请使用以下方法之一。
Azure 门户
在 Azure 门户中,转到服务总线命名空间。
在左侧菜单中,选择 “共享访问策略”。
从列表中选择策略。 这些步骤使用 RootManageSharedAccessKey 作为示例。
若要重新生成主密钥,请在 SAS 策略:RootManageSharedAccessKey 窗格中,选择命令栏上的 “重新生成主密钥 ”。
若要重新生成辅助密钥,请在 SAS 策略:RootManageSharedAccessKey 窗格中,选择命令栏上的省略号(...),然后选择“ 重新生成辅助密钥”。
Azure PowerShell
如果使用 Azure PowerShell,请使用 New-AzServiceBusKey cmdlet 重新生成服务总线命名空间的主密钥和辅助密钥。 还可以使用 -KeyValue 参数为重新生成的主密钥和辅助密钥指定值。
Azure CLI
如果使用 Azure CLI,请使用 az servicebus namespace authorization-rule keys renew 命令重新生成服务总线命名空间的主密钥和辅助密钥。 还可以使用 --key-value 参数为重新生成的主密钥和辅助密钥指定值。
使用服务总线进行 SAS 身份验证
以下方案包括授权规则的配置、SAS 令牌的生成和客户端授权。
有关说明配置和使用 SAS 授权的服务总线应用程序的示例,请参阅 CRUD 操作。
访问实体上的 SAS 规则
使用服务总线管理库中队列或主题的获取或更新操作来访问和更新相应的共享访问授权规则。 还可以使用这些库创建队列或主题时添加规则。
使用 SAS 授权
使用任何官方支持语言中的任何服务总线 SDK 的应用程序可以通过传递给客户端构造函数的连接字符串来使用 SAS 授权。 支持的语言包括 .NET、Java、JavaScript 和 Python。
连接字符串可以包括规则名称(SharedAccessKeyName)和规则键(SharedAccessKey)或以前颁发的令牌(SharedAccessSignature)。 当这些项存在于传递给接受连接字符串的任何构造函数或工厂方法的连接字符串中时,会自动创建和填充 SAS 令牌提供程序。
要使用服务总线订阅的 SAS 授权,可以使用服务总线命名空间或主题上配置的 SAS 密钥。
在 HTTP 级别使用共享访问签名
了解如何为服务总线中的任何实体创建共享访问签名后,即可执行 HTTP POST 请求:
POST https://<yournamespace>.servicebus.chinacloudapi.cn/<yourentity>/messages
Content-Type: application/json
Authorization: SharedAccessSignature sr=https%3A%2F%2F<yournamespace>.servicebus.chinacloudapi.cn%2F<yourentity>&sig=<yoursignature from code above>&se=1438205742&skn=KeyName
ContentType: application/atom+xml;type=entry;charset=utf-8
请记住,此方法适用于所有事物。 可以为队列、主题或订阅创建 SAS。
如果为发送方或客户端提供 SAS 令牌,则它没有直接密钥,并且无法反转哈希来获取它。 你可以控制发送方或客户端可以访问的内容以及访问时间。 需要记住的一个重要事项是,如果更改策略中的主密钥,则从策略中创建的任何共享访问签名都失效。
在 AMQP 级别使用共享访问签名
上一部分介绍了如何将 SAS 令牌与 HTTP POST 请求配合使用,以便将数据发送到服务总线。 可以使用高级消息队列协议(AMQP)访问服务总线。 在许多情况下,AMQP 是出于性能原因使用的首选协议。 AMQP 的 SAS 令牌用法在文档AMQP Claim-Based Security Version 1.0中进行描述。
在发布者开始将数据发送到服务总线之前,它必须将 AMQP 消息中的 SAS 令牌发送到名为 $cbs 的明确 AMQP 节点。 这是服务用于获取和验证所有 SAS 令牌的特殊队列。
发布者必须在 AMQP 消息中指定 ReplyTo 字段。 它是服务中的一个节点,通过令牌验证结果回复发布者,该过程是发布者和服务之间的简单请求/回复模式。 如 AMQP 1.0 规范所述,此回复节点是动态创建的。 发布者检查 SAS 令牌是否有效后,发布者可以开始将数据发送到服务。
以下步骤演示如何使用 AMQP.NET Lite 库通过 AMQP 协议发送 SAS 令牌。 如果在 C# 中开发时无法使用官方服务总线 SDK(例如,在 WinRT、.NET Compact Framework、.NET Micro Framework 和 Mono 上),则此库非常有用。 该库还有助于了解基于声明的安全性在 AMQP 级别的工作原理。 你看到了它在 HTTP 级别的工作原理,HTTP POST 请求和 SAS 令牌在 Authorization 标头中发送。
如果不需要有关 AMQP 的深入知识,则可以使用任何受支持的语言(如 .NET、Java、JavaScript、Python 和 Go)的官方服务总线 SDK。 SDK 将为你执行此操作。
C#
/// <summary>
/// Send claim-based security (CBS) token
/// </summary>
/// <param name="shareAccessSignature">Shared access signature (token) to send</param>
private bool PutCbsToken(Connection connection, string sasToken)
{
bool result = true;
Session session = new Session(connection);
string cbsClientAddress = "cbs-client-reply-to";
var cbsSender = new SenderLink(session, "cbs-sender", "$cbs");
var cbsReceiver = new ReceiverLink(session, cbsClientAddress, "$cbs");
// construct the put-token message
var request = new Message(sasToken);
request.Properties = new Properties();
request.Properties.MessageId = Guid.NewGuid().ToString();
request.Properties.ReplyTo = cbsClientAddress;
request.ApplicationProperties = new ApplicationProperties();
request.ApplicationProperties["operation"] = "put-token";
request.ApplicationProperties["type"] = "servicebus.chinacloudapi.cn:sastoken";
request.ApplicationProperties["name"] = Fx.Format("amqp://{0}/{1}", sbNamespace, entity);
cbsSender.Send(request);
// receive the response
var response = cbsReceiver.Receive();
if (response == null || response.Properties == null || response.ApplicationProperties == null)
{
result = false;
}
else
{
int statusCode = (int)response.ApplicationProperties["status-code"];
if (statusCode != (int)HttpStatusCode.Accepted && statusCode != (int)HttpStatusCode.OK)
{
result = false;
}
}
// the sender/receiver might be kept open for refreshing tokens
cbsSender.Close();
cbsReceiver.Close();
session.Close();
return result;
}
该方法 PutCbsToken() 接收 连接 (AMQP 连接类实例,由 AMQP .NET Lite 库提供)。 此实例表示与服务的 TCP 连接,以及表示要发送的 SAS 令牌的sasToken参数。
注意
请务必创建连接,并将简单身份验证和安全层(SASL)的身份验证机制设置为ANONYMOUS。 如果不需要发送 SAS 令牌,不要将机制设置为使用用户名和密码的默认值 PLAIN。
接下来,发布者将创建两个 AMQP 链接来发送 SAS 令牌和接收来自服务的回复(此令牌验证结果)。
AMQP 消息包含一组属性和详细信息,而不是简单的消息:
- SAS 令牌是消息的正文(使用其构造函数)。
- 该
ReplyTo属性设置为节点名称,用于接收接收方链接上的验证结果。 如果需要,可以更改其名称,服务会动态创建它。 - 该服务使用最后三个应用程序/自定义属性来指示它必须执行的操作类型。 如 AMQP 基于声明的安全草案规范中所述,它们必须是:
- 操作名称 (
put-token) - 令牌类型(在本例中,
servicebus.chinacloudapi.cn:sastoken) - 令牌应用到的受众的名称(整个实体)
- 操作名称 (
发布者在发送方链接上发送 SAS 令牌后,必须在接收方链接上读取回复。 答复是一个简单的 AMQP 消息,其中包含名为 status-code 的应用程序属性。 此属性可以包含与 HTTP 状态代码相同的值。
服务总线操作所需的权限
下表显示对服务总线资源进行各种操作所需的访问权限。
| 操作 | 需要索赔 | 声明范围 |
|---|---|---|
| 命名空间 | ||
| 在命名空间上配置授权规则 | 管理 | 任何命名空间地址 |
| 服务注册表 | ||
| 枚举私有策略 | 管理 | 任何命名空间地址 |
| 开始在命名空间上侦听 | 听 | 任何命名空间地址 |
| 将消息发送到某命名空间中的侦听器 | 发送 | 任何命名空间地址 |
| 队列 | ||
| 创建队列 | 管理 | 任何命名空间地址 |
| 删除队列 | 管理 | 任何有效队列地址 |
| 枚举队列 | 管理 | /$Resources/Queues |
| 获取队列描述 | 管理 | 任何有效队列地址 |
| 为队列配置授权规则 | 管理 | 任何有效队列地址 |
| 检查队列是否存在 | 管理 | 任何有效队列地址 |
| 发送至队列 | 发送 | 任何有效队列地址 |
| 从队列接收消息 | 监听 | 任何有效队列地址 |
| 在窥视锁定模式下接收消息后丢弃或完成消息 | 听 | 任何有效队列地址 |
| 推迟消息以供将来检索 | 倾听 | 任何有效队列地址 |
| 将消息放入死信队列 | 听 | 任何有效队列地址 |
| 获取与消息队列会话关联的状态信息 | 倾听 | 任何有效队列地址 |
| 设置与消息队列会话关联的状态 | 倾听 | 任何有效队列地址 |
| 将消息安排为稍后发送 | 听 | 任何有效队列地址 |
| 主题 | ||
| 创建主题 | 管理 | 任何命名空间地址 |
| 删除主题 | 管理 | 任何有效主题地址 |
| 枚举主题 | 管理 | /$Resources/Topics |
| 获取主题描述 | 管理 | 任何有效主题地址 |
| 在主题上配置授权规则 | 管理 | 任何有效主题地址 |
| 发送到主题 | 发送 | 任何有效主题地址 |
| 订阅 | ||
| 创建订阅 | 管理 | 任何命名空间地址 |
| 删除订阅 | 管理 | ../myTopic/Subscriptions/mySubscription |
| 枚举订阅 | 管理 | ../myTopic/Subscriptions |
| 获取订阅说明 | 管理 | ../myTopic/Subscriptions/mySubscription |
| 在抢先锁定模式下接收到消息后放弃或完成消息 | 监听 | ../myTopic/Subscriptions/mySubscription |
| 推迟消息以供将来检索 | 听 | ../myTopic/Subscriptions/mySubscription |
| 将消息标记为死信 | 监听 | ../myTopic/Subscriptions/mySubscription |
| 获取与主题会话关联的状态 | 听 | ../myTopic/Subscriptions/mySubscription |
| 设置与主题会话关联的状态 | 听 | ../myTopic/Subscriptions/mySubscription |
| 规则 | ||
| 创建规则 | 倾听 | ../myTopic/Subscriptions/mySubscription |
| 删除规则 | 侦听 | ../myTopic/Subscriptions/mySubscription |
| 枚举规则 | 管理或监听 | ../myTopic/Subscriptions/mySubscription/Rules |
相关内容
若要了解有关服务总线消息传送的详细信息,请参阅以下主题: