通过使用共享访问签名(SAS),你可以控制授予客户端的访问类型。 你可以在SAS中设置以下控制:
- SAS 处于有效状态的时间间隔,包括开始时间和到期时间。
- SAS 授予的权限。 例如,事件中心命名空间的SAS可能允许监听事件,但不允许发送事件。
SAS认证提供了以下优势:
- 只有可提供有效凭据的客户端才能将数据发送到事件中心。
- 一个客户端无法模拟另一个客户端。
- 你可以阻止流氓客户端向事件中心发送数据。
本文介绍如何使用 SAS 对事件中心资源访问进行身份验证。 若要了解如何使用 SAS 授权对事件中心资源的访问,请参阅此文。
注意
作为安全最佳实践,尽量使用Microsoft Entra凭证,而非共享访问签名,因为共享访问签名更容易被攻破。 虽然可以继续使用共享访问签名(SAS)来授予对事件中心资源的精细访问权限,但Microsoft Entra ID提供了类似的功能,而无需管理 SAS 令牌或担心撤销已泄露的 SAS。
有关 Azure 事件中心中 Microsoft Entra 集成的更多信息,请参阅 使用 Microsoft Entra ID 授权访问事件中心。
配置SAS认证
可以在事件中心命名空间或实体(事件中心或 Kafka 主题)中配置 SAS 规则。 不支持在消费者组上配置SAS规则,但你可以使用在命名空间或实体上配置的规则来保护对消费者组的访问。 下图显示了如何对示例实体应用授权规则。
在此示例中,示例事件中心命名空间 (ExampleNamespace) 有两个实体:eh1 和 Kafka topic1。 授权规则是同时在实体级别和命名空间级别定义的。
manageRuleNS、sendRuleNS 和 listenRuleNS 授权规则将同时应用到 eh1 和 topic1。 listenRule-eh 和 sendRule-eh 授权规则仅应用到 eh1,sendRuleT 授权规则仅应用到 topic1。
使用 sendRuleNS 授权规则时,客户端应用程序可以同时向 eh1 和 topic1 发送消息。 使用 sendRuleT 授权规则时,会强制要求仅对 topic1 进行粒度访问,因此,使用此规则进行访问的客户端应用程序现在无法向 eh1 发送消息,而只能向 topic1 发送消息。
生成共享访问签名令牌
任何有权访问授权规则名称及其签名密钥的客户端都可以生成 SAS 令牌。 通过生成以下格式的字符串来生成该标记:
-
se- 令牌过期时刻。 整数反映的是自1970年1月1日UTC纪元00:00:00(UNIX纪元)令牌到期后的秒数。 -
skn- 授权规则的名称,即 SAS 密钥名称。 -
sr- 正在访问的资源的 URI。 -
sig- 签名。
签名字符串是基于资源URI(如前节所述的作用域)计算的 HMAC-SHA256 哈希值,以及令牌到期瞬时的字符串表示,中间以换行(LF)分隔。 使用授权规则的其中一个签名密钥对哈希进行签名。 哈希计算返回256位/32字节的哈希值。
HMAC-SHA256(signingKey, 'https://<yournamespace>.servicebus.chinacloudapi.cn/' + '\n' + 1438205742)
令牌包含非哈希值,接收方可以用相同参数重新计算哈希值,并验证发行方是否拥有有效的签名密钥。
资源 URI 是声明对 服务总线 资源访问权限的完整 URI。 例如, http://<namespace>.servicebus.chinacloudapi.cn/<entityPath> 或 sb://<namespace>.servicebus.chinacloudapi.cn/<entityPath>,如 http://contoso.servicebus.chinacloudapi.cn/eh1。
对URI进行百分比编码。
配置用于登录该URI或其分层父实体的SAS规则。 例如,之前示例中的 http://contoso.servicebus.chinacloudapi.cn/eh1 或 http://contoso.servicebus.chinacloudapi.cn。
SAS 令牌对 signature-string 中使用的以 <resourceURI> 为前缀的所有资源均有效。
注意
你通过使用共享访问策略为事件中心生成访问令牌。 有关详细信息,请参阅共享访问授权策略。
从策略生成签名(令牌)
以下部分展示了如何使用共享访问签名策略生成SAS令牌。
Node.js
function createSharedAccessToken(uri, saName, saKey) {
if (!uri || !saName || !saKey) {
throw "Missing required parameter";
}
var encoded = encodeURIComponent(uri);
var now = new Date();
var week = 60*60*24*7;
var ttl = Math.round(now.getTime() / 1000) + week;
var signature = encoded + '\n' + ttl;
var hash = crypto.createHmac('sha256', saKey).update(signature, 'utf8').digest('base64');
return 'SharedAccessSignature sr=' + encoded + '&sig=' +
encodeURIComponent(hash) + '&se=' + ttl + '&skn=' + saName;
}
若要使用策略名称和键值连接到事件中心,请使用采用 EventHubProducerClient 参数的 AzureNamedKeyCredential 构造函数。
const producer = new EventHubProducerClient("NAMESPACE NAME.servicebus.chinacloudapi.cn", eventHubName, new AzureNamedKeyCredential("POLICYNAME", "KEYVALUE"));
添加对 AzureNamedKeyCredential 的引用。
const { AzureNamedKeyCredential } = require("@azure/core-auth");
要使用通过代码生成的 SAS 令牌,请使用接受 AzureSASCredential 参数的 EventHubProducerClient 构造函数。
var token = createSharedAccessToken("https://NAMESPACENAME.servicebus.chinacloudapi.cn", "POLICYNAME", "KEYVALUE");
const producer = new EventHubProducerClient("NAMESPACENAME.servicebus.chinacloudapi.cn", eventHubName, new AzureSASCredential(token));
添加对 AzureSASCredential 的引用。
const { AzureSASCredential } = require("@azure/core-auth");
Java
private static String GetSASToken(String resourceUri, String keyName, String key)
{
long epoch = System.currentTimeMillis()/1000L;
int week = 60*60*24*7;
String expiry = Long.toString(epoch + week);
String sasToken = null;
try {
String stringToSign = URLEncoder.encode(resourceUri, "UTF-8") + "\n" + expiry;
String signature = getHMAC256(key, stringToSign);
sasToken = "SharedAccessSignature sr=" + URLEncoder.encode(resourceUri, "UTF-8") +"&sig=" +
URLEncoder.encode(signature, "UTF-8") + "&se=" + expiry + "&skn=" + keyName;
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return sasToken;
}
public static String getHMAC256(String key, String input) {
Mac sha256_HMAC = null;
String hash = null;
try {
sha256_HMAC = Mac.getInstance("HmacSHA256");
SecretKeySpec secret_key = new SecretKeySpec(key.getBytes(), "HmacSHA256");
sha256_HMAC.init(secret_key);
Encoder encoder = Base64.getEncoder();
hash = new String(encoder.encode(sha256_HMAC.doFinal(input.getBytes("UTF-8"))));
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return hash;
}
PHP
function generateSasToken($uri, $sasKeyName, $sasKeyValue)
{
$targetUri = strtolower(rawurlencode(strtolower($uri)));
$expires = time();
$expiresInMins = 60;
$week = 60*60*24*7;
$expires = $expires + $week;
$toSign = $targetUri . "\n" . $expires;
$signature = rawurlencode(base64_encode(hash_hmac('sha256',
$toSign, $sasKeyValue, TRUE)));
$token = "SharedAccessSignature sr=" . $targetUri . "&sig=" . $signature . "&se=" . $expires . "&skn=" . $sasKeyName;
return $token;
}
C#
private static string createToken(string resourceUri, string keyName, string key)
{
TimeSpan sinceEpoch = DateTime.UtcNow - new DateTime(1970, 1, 1);
var week = 60 * 60 * 24 * 7;
var expiry = Convert.ToString((int)sinceEpoch.TotalSeconds + week);
string stringToSign = HttpUtility.UrlEncode(resourceUri) + "\n" + expiry;
using (var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(key)))
{
var signature = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign)));
var sasToken = String.Format(CultureInfo.InvariantCulture, "SharedAccessSignature sr={0}&sig={1}&se={2}&skn={3}", HttpUtility.UrlEncode(resourceUri), HttpUtility.UrlEncode(signature), expiry, keyName);
return sasToken;
}
}
PowerShell
[Reflection.Assembly]::LoadWithPartialName("System.Web")| out-null
$URI="myNamespace.servicebus.chinacloudapi.cn/myEventHub/"
$Access_Policy_Name="RootManageSharedAccessKey"
$Access_Policy_Key="myPrimaryKey"
#Token expires now+300
$Expires=([DateTimeOffset]::Now.ToUnixTimeSeconds())+300
$SignatureString=[System.Web.HttpUtility]::UrlEncode($URI)+ "`n" + [string]$Expires
$HMAC = New-Object System.Security.Cryptography.HMACSHA256
$HMAC.key = [Text.Encoding]::ASCII.GetBytes($Access_Policy_Key)
$Signature = $HMAC.ComputeHash([Text.Encoding]::ASCII.GetBytes($SignatureString))
$Signature = [Convert]::ToBase64String($Signature)
$SASToken = "SharedAccessSignature sr=" + [System.Web.HttpUtility]::UrlEncode($URI) + "&sig=" + [System.Web.HttpUtility]::UrlEncode($Signature) + "&se=" + $Expires + "&skn=" + $Access_Policy_Name
$SASToken
Bash
get_sas_token() {
local EVENTHUB_URI='EVENTHUBURI'
local SHARED_ACCESS_KEY_NAME='SHAREDACCESSKEYNAME'
local SHARED_ACCESS_KEY='SHAREDACCESSKEYVALUE'
local EXPIRY=${EXPIRY:=$((60 * 60 * 24))} # Default token expiry is 1 day
local ENCODED_URI=$(echo -n $EVENTHUB_URI | jq -s -R -r @uri)
local TTL=$(($(date +%s) + $EXPIRY))
local UTF8_SIGNATURE=$(printf "%s\n%s" $ENCODED_URI $TTL | iconv -t utf8)
local HASH=$(echo -n "$UTF8_SIGNATURE" | openssl sha256 -hmac $SHARED_ACCESS_KEY -binary | base64)
local ENCODED_HASH=$(echo -n $HASH | jq -s -R -r @uri)
echo -n "SharedAccessSignature sr=$ENCODED_URI&sig=$ENCODED_HASH&se=$TTL&skn=$SHARED_ACCESS_KEY_NAME"
}
使用 SAS 对事件中心发布者进行身份验证
事件发布者定义事件中心的虚拟终结点。 发布者只可用于将消息发送到事件中心,而不可用于接收消息。
通常,事件中心为每个客户端使用一个发布者。 发送到事件中心的任何发布者的所有消息都会在该事件中心内排队。 发布者可以实现精细访问控制。
为每个事件中心客户端分配了一个唯一令牌,并将其分配给客户端。 生成令牌的方式是,每个唯一令牌都授予对各自唯一发布者的访问权限。 持有令牌的客户端只能向一个发布者发送消息,但不能向其他发布者发送消息。 如果多个客户端共享同一令牌,则其中每个客户端都会共享该发布者。
为所有令牌分配 SAS 密钥。 通常,所有令牌使用同一密钥进行签名。 客户端不知道密钥,这可以防止客户端制造令牌。 客户端以相同的令牌运行,直到令牌过期。
例如,要定义仅发送或发布到事件中心的授权规则,你需要定义发送授权规则。 你可以在命名空间层面创建规则,或者为特定实体(如事件中心实例或主题)赋予更细致的范围。 拥有这种细粒访问的客户端或应用程序称为事件中心发布者。 要设置此认证,请按照以下步骤操作:
在你想发布的实体上创建一个 SAS 密钥,并为其分配 发送 范围。 有关详细信息,请参阅共享访问授权策略。
用你在第一步创建的密钥,生成一个带有特定发布者有效期的SAS令牌。 关于示例代码,请参见 “从策略生成签名(令牌)”。
将令牌提供给发布者客户端,发布者只能发送给令牌授予访问权限的实体和发布者。
当令牌到期时,客户端将失去向该实体发送或发布的权限。
注意
虽然我们不推荐这样做,但你可以为设备配备代币,从而获得访问事件中心或命名空间的权限。 持有此令牌的任何设备都可以直接将消息发送到该事件中心。 你不能将该设备列入阻止名单,禁止其向该事件中心发送数据。
建议提供特定和具体的范围。
重要
创建代币后,每个客户端都会获得独特的代币。
当客户端向事件中心发送数据时,它会用该令牌标记请求。 为了防止攻击者窃听和盗取令牌,客户端与事件中心之间的通信必须通过加密通道进行。
如果攻击者窃取了令牌,攻击者可以冒充被盗令牌的客户端。 如果你不允许某个发布者,该客户端将无法使用,直到收到使用其他发布商的新令牌。
使用 SAS 对 Event Hubs 使用者进行身份验证
若要对使用事件中心生成者生成的数据的后端应用程序进行身份验证,事件中心令牌身份验证要求其客户端具有分配给其事件中心命名空间或事件中心实例或 主题的管理权限 或 侦听 权限。 通过使用者组使用事件中心内的数据。 尽管 SAS 策略可以提供精细范围,但此范围仅仅是在实体级别定义的,而不是在使用者级别定义的。 这意味着,在命名空间级别或者事件中心或主题级别定义的特权将应用到该实体的使用者组。
禁用本地或SAS密钥认证
对于某些组织安全要求,需要完全禁用本地/SAS 密钥身份验证,并依赖于基于Microsoft Entra ID的身份验证,这是连接Azure 事件中心的建议方法。 可以使用Azure门户或Azure 资源管理器模板在事件中心命名空间级别禁用本地/SAS 密钥身份验证。
通过使用门户禁用本地或SAS密钥认证
可以使用 Azure 门户为给定的事件中心命名空间禁用本地/SAS 密钥身份验证。
使用模板禁用本地或SAS密钥认证
可以通过将 disableLocalAuth 属性设置为 true来禁用给定事件中心命名空间的本地身份验证,如以下Azure 资源管理器模板(ARM 模板)中所示。
{
"apiVersion": "2024-01-01",
"name": "[parameters('eventHubNamespaceName')]",
"type": "Microsoft.EventHub/Namespaces",
"location": "[resourceGroup().location]",
"sku": {
"name": "Standard",
"tier": "Standard"
},
"properties": {
"isAutoInflateEnabled": true,
"maximumThroughputUnits": 7,
"disableLocalAuth": true
},
"resources": [
{
"apiVersion": "2024-01-01",
"name": "[parameters('eventHubName')]",
"type": "EventHubs",
"dependsOn": [
"[concat('Microsoft.EventHub/namespaces/', parameters('eventHubNamespaceName'))]"
],
"properties": {
"messageRetentionInDays": "[parameters('messageRetentionInDays')]",
"partitionCount": "[parameters('partitionCount')]"
}
}
]
}
示例
- 请参阅此 GitHub 位置中的 .NET 示例 #6,了解如何使用共享访问凭据或默认 Azure 凭据将事件发布到事件中心。
- 请参阅此GitHub位置中的 .NET 示例 #5,了解如何使用共享访问凭据或默认 Azure 凭据身份来消费或处理事件。
相关内容
现在你已经了解了SAS认证,请浏览以下相关文章:
- 使用共享访问签名授权访问事件中心资源 ——学习授权概念。
- 授权使用 Microsoft Entra ID 访问事件中心——实现企业级安全。