你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn。
如何保护 Webhook 终结点
确保从头到尾传递消息对于确保系统之间传输的敏感信息的保密性、完整性和可信度至关重要。 你信任从远程系统接收的信息的能力和意愿依赖于提供其标识的发件人。 调用自动化有两种方法来传达可以保护的事件;Azure 事件网格发送的共享 IncomingCall 事件,以及呼叫自动化平台通过 Webhook 发送的所有其他中呼叫事件。
传入呼叫事件
Azure 通信服务依赖于Azure 事件网格订阅来传递 IncomingCall 事件。 请参阅Azure 事件网格团队,获取有关如何保护 Webhook 订阅的文档。
呼叫自动化 Webhook 事件
呼叫自动化事件 将发送到在接听呼叫时指定的 Webhook 回调 URI,或发出新的出站呼叫。 回调 URI 必须是具有有效 HTTPS 证书、DNS 名称和 IP 地址的公共终结点,其防火墙端口正确打开,使呼叫自动化能够访问它。 如果不采取必要的步骤来保护它免受未经授权的访问,此匿名公共 Web 服务器可能会造成安全风险。
提高此安全性的一种常见方法是实现 API 密钥机制。 Web 服务器可以在运行时生成密钥,并在应答或创建调用时将其作为查询参数在回调 URI 中提供。 Web 服务器可以在允许访问之前从调用自动化验证 Webhook 回调中的密钥。 某些客户需要更多的安全措施。 在这些情况下,外围网络设备可以验证与 Web 服务器或应用程序本身分开的入站 Webhook。 仅 API 密钥机制可能是不够的。
改进调用自动化 Webhook 回调安全性
调用自动化发送的每个中调用 Webhook 回调在入站 HTTPS 请求的身份验证标头中使用已签名的 JSON Web 令牌 (JWT)。 可以使用标准 Open ID 连接 (OIDC) JWT 验证技术来确保令牌的完整性,如下所示。 JWT 的生存期为 5(5)分钟,为发送到回调 URI 的每个事件创建一个新令牌。
- 获取 Open ID 配置 URL: https://acscallautomation.communication.azure.com/calling/.well-known/acsopenidconfiguration
- 安装 Microsoft.AspNetCore.Authentication.JwtBearer NuGet 包。
- 将应用程序配置为使用 NuGet 包和Azure 通信服务资源的配置来验证 JWT。 需要值
audience
,因为它存在于 JWT 有效负载中。 - 验证颁发者、受众和 JWT 令牌。
- 受众是用于设置呼叫自动化客户端的Azure 通信服务资源 ID。 请参阅 此处 ,了解如何获取它。
- OpenId 配置中的 JSON Web 密钥集(JWKS)终结点包含用于验证 JWT 令牌的密钥。 当签名有效且令牌未过期(在生成后的 5 分钟内),客户端可以使用令牌进行授权。
此示例代码演示如何用于 Microsoft.IdentityModel.Protocols.OpenIdConnect
验证 Webhook 有效负载
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Protocols;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.Tokens;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
// Add Azure Communication Services CallAutomation OpenID configuration
var configurationManager = new ConfigurationManager<OpenIdConnectConfiguration>(
builder.Configuration["OpenIdConfigUrl"],
new OpenIdConnectConfigurationRetriever());
var configuration = configurationManager.GetConfigurationAsync().Result;
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Configuration = configuration;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidAudience = builder.Configuration["AllowedAudience"]
};
});
builder.Services.AddAuthorization();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.MapPost("/api/callback", (CloudEvent[] events) =>
{
// Your implemenation on the callback event
return Results.Ok();
})
.RequireAuthorization()
.WithOpenApi();
app.UseAuthentication();
app.UseAuthorization();
app.Run();
改进调用自动化 Webhook 回调安全性
调用自动化发送的每个中调用 Webhook 回调在入站 HTTPS 请求的身份验证标头中使用已签名的 JSON Web 令牌 (JWT)。 可以使用标准 Open ID 连接 (OIDC) JWT 验证技术来确保令牌的完整性,如下所示。 JWT 的生存期为 5(5)分钟,为发送到回调 URI 的每个事件创建一个新令牌。
- 获取 Open ID 配置 URL: https://acscallautomation.communication.azure.com/calling/.well-known/acsopenidconfiguration
- 以下示例使用 Spring 框架,使用 Spring initializr 和 Maven 作为项目生成工具创建。
- 在以下
pom.xml
依赖项中添加:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-jose</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-resource-server</artifactId>
</dependency>
- 将应用程序配置为验证 JWT 和Azure 通信服务资源的配置。 需要值
audience
,因为它存在于 JWT 有效负载中。 - 验证颁发者、受众和 JWT 令牌。
- 受众是用于设置呼叫自动化客户端的Azure 通信服务资源 ID。 请参阅 此处 ,了解如何获取它。
- OpenId 配置中的 JSON Web 密钥集(JWKS)终结点包含用于验证 JWT 令牌的密钥。 当签名有效且令牌未过期(在生成后的 5 分钟内),客户端可以使用令牌进行授权。
此示例代码演示如何配置 OIDC 客户端以使用 JWT 验证 Webhook 有效负载
package callautomation.example.security;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult;
import org.springframework.security.oauth2.jwt.*;
@EnableWebSecurity
public class TokenValidationConfiguration {
@Value("ACS resource ID")
private String audience;
@Value("https://acscallautomation.communication.azure.com")
private String issuer;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeRequests()
.mvcMatchers("/api/callbacks").permitAll()
.anyRequest()
.and()
.oauth2ResourceServer()
.jwt()
.decoder(jwtDecoder());
return http.build();
}
class AudienceValidator implements OAuth2TokenValidator<Jwt> {
private String audience;
OAuth2Error error = new OAuth2Error("invalid_token", "The required audience is missing", null);
public AudienceValidator(String audience) {
this.audience = audience;
}
@Override
public OAuth2TokenValidatorResult validate(Jwt token) {
if (token.getAudience().contains(audience)) {
return OAuth2TokenValidatorResult.success();
} else {
return OAuth2TokenValidatorResult.failure(error);
}
}
}
JwtDecoder jwtDecoder() {
OAuth2TokenValidator<Jwt> withAudience = new AudienceValidator(audience);
OAuth2TokenValidator<Jwt> withIssuer = JwtValidators.createDefaultWithIssuer(issuer);
OAuth2TokenValidator<Jwt> validator = new DelegatingOAuth2TokenValidator<>(withAudience, withIssuer);
NimbusJwtDecoder jwtDecoder = (NimbusJwtDecoder) JwtDecoders.fromOidcIssuerLocation(issuer);
jwtDecoder.setJwtValidator(validator);
return jwtDecoder;
}
}
改进调用自动化 Webhook 回调安全性
调用自动化发送的每个中调用 Webhook 回调在入站 HTTPS 请求的身份验证标头中使用已签名的 JSON Web 令牌 (JWT)。 可以使用标准 Open ID 连接 (OIDC) JWT 验证技术来确保令牌的完整性,如下所示。 JWT 的生存期为 5(5)分钟,为发送到回调 URI 的每个事件创建一个新令牌。
- 获取 Open ID 配置 URL: https://acscallautomation.communication.azure.com/calling/.well-known/acsopenidconfiguration
- 安装以下包:
npm install express jwks-rsa jsonwebtoken
- 将应用程序配置为验证 JWT 和Azure 通信服务资源的配置。 需要值
audience
,因为它存在于 JWT 有效负载中。 - 验证颁发者、受众和 JWT 令牌。
- 受众是用于设置呼叫自动化客户端的Azure 通信服务资源 ID。 请参阅 此处 ,了解如何获取它。
- OpenId 配置中的 JSON Web 密钥集(JWKS)终结点包含用于验证 JWT 令牌的密钥。 当签名有效且令牌未过期(在生成后的 5 分钟内),客户端可以使用令牌进行授权。
此示例代码演示如何配置 OIDC 客户端以使用 JWT 验证 Webhook 有效负载
import express from "express";
import { JwksClient } from "jwks-rsa";
import { verify } from "jsonwebtoken";
const app = express();
const port = 3000;
const audience = "ACS resource ID";
const issuer = "https://acscallautomation.communication.azure.com";
app.use(express.json());
app.post("/api/callback", (req, res) => {
const token = req?.headers?.authorization?.split(" ")[1] || "";
if (!token) {
res.sendStatus(401);
return;
}
try {
verify(
token,
(header, callback) => {
const client = new JwksClient({
jwksUri: "https://acscallautomation.communication.azure.com/calling/keys",
});
client.getSigningKey(header.kid, (err, key) => {
const signingKey = key?.publicKey || key?.rsaPublicKey;
callback(err, signingKey);
});
},
{
audience,
issuer,
algorithms: ["RS256"],
});
// Your implementation on the callback event
res.sendStatus(200);
} catch (error) {
res.sendStatus(401);
}
});
app.listen(port, () => {
console.log(`Server running on port ${port}`);
});
改进调用自动化 Webhook 回调安全性
调用自动化发送的每个中调用 Webhook 回调在入站 HTTPS 请求的身份验证标头中使用已签名的 JSON Web 令牌 (JWT)。 可以使用标准 Open ID 连接 (OIDC) JWT 验证技术来确保令牌的完整性,如下所示。 JWT 的生存期为 5(5)分钟,为发送到回调 URI 的每个事件创建一个新令牌。
- 获取 Open ID 配置 URL: https://acscallautomation.communication.azure.com/calling/.well-known/acsopenidconfiguration
- 安装以下包:
pip install flask pyjwt
- 将应用程序配置为验证 JWT 和Azure 通信服务资源的配置。 需要值
audience
,因为它存在于 JWT 有效负载中。 - 验证颁发者、受众和 JWT 令牌。
- 受众是用于设置呼叫自动化客户端的Azure 通信服务资源 ID。 请参阅 此处 ,了解如何获取它。
- OpenId 配置中的 JSON Web 密钥集(JWKS)终结点包含用于验证 JWT 令牌的密钥。 当签名有效且令牌未过期(在生成后的 5 分钟内),客户端可以使用令牌进行授权。
此示例代码演示如何配置 OIDC 客户端以使用 JWT 验证 Webhook 有效负载
from flask import Flask, jsonify, abort, request
import jwt
app = Flask(__name__)
@app.route("/api/callback", methods=["POST"])
def handle_callback_event():
token = request.headers.get("authorization").split()[1]
if not token:
abort(401)
try:
jwks_client = jwt.PyJWKClient(
"https://acscallautomation.communication.azure.com/calling/keys"
)
jwt.decode(
token,
jwks_client.get_signing_key_from_jwt(token).key,
algorithms=["RS256"],
issuer="https://acscallautomation.communication.azure.com",
audience="ACS resource ID",
)
# Your implementation on the callback event
return jsonify(success=True)
except jwt.InvalidTokenError:
print("Token is invalid")
abort(401)
except Exception as e:
print("uncaught exception" + e)
abort(500)
if __name__ == "__main__":
app.run()
后续步骤
- 详细了解 如何使用呼叫自动化来控制和引导呼叫。