Sdílet prostřednictvím


Zabezpečení koncových bodů webhooků a připojení websocket

Zabezpečení doručování zpráv od konce do konce je zásadní pro zajištění důvěrnosti, integrity a důvěryhodnosti citlivých informací přenášených mezi systémy. Vaše schopnost a ochota důvěřovat informacím přijatým ze vzdáleného systému závisí na tom, že odesílatel poskytne svoji identitu. Automatizace volání má dva způsoby komunikace událostí, které je možné zabezpečit; sdílenou událost IncomingCall odesílanou službou Azure Event Grid a všechny ostatní události volání uprostřed volání odesílané platformou Automatizace volání prostřednictvím webhooku.

Událost příchozího volání

Služba Azure Communication Services spoléhá na odběry služby Azure Event Grid k doručení události IncomingCall. Informace o zabezpečení předplatného webhooku najdete v týmu Azure Event Gridu.

Události webhooku služby Call Automation

Události automatizace volání se odesílají do webhook URI pro zpětné volání zadaného při přijetí hovoru nebo při uskutečňování nového odchozího hovoru. Identifikátor URI zpětného volání musí být veřejným koncovým bodem s platným certifikátem HTTPS, názvem DNS a IP adresou, se správně otevřenými porty brány firewall, aby k němu mohla služba automatizace volání přistupovat. Tento anonymní veřejný webový server může vytvořit bezpečnostní riziko, pokud neuděláte potřebné kroky k zabezpečení před neoprávněným přístupem.

Běžným způsobem, jak toto zabezpečení zlepšit, je implementace mechanismu KLÍČE rozhraní API. Váš webový server může klíč vygenerovat za běhu a poskytnout ho v identifikátoru URI zpětného volání jako parametr dotazu, když přijmete nebo vytvoříte volání. Váš webový server může před povolením přístupu ověřit klíč ve zpětném volání webhooku z Call Automation. Někteří zákazníci vyžadují větší bezpečnostní opatření. V těchto případech může hraniční síťové zařízení ověřit příchozí webhook odděleně od samotného webového serveru nebo aplikace. Samotný mechanismus klíče rozhraní API nemusí být dostatečný.

Vylepšení zabezpečení zpětného volání webhooku pro automatizované hovory

Každé zpětné volání webhooku během hovoru odeslané službou Call Automation používá podepsaný webový token JSON (JWT) v hlavičce ověřování příchozího požadavku HTTPS. K zajištění integrity tokenu můžete použít standardní techniky ověřování Open ID Connect (OIDC) JWT, jak je uvedeno dále. Životnost JWT je pět (5) minut a pro každou událost odesílanou do identifikátoru URI zpětného volání se vytvoří nový token.

  1. Získejte adresu URL konfigurace Open ID: https://acscallautomation.communication.azure.com/calling/.well-known/acsopenidconfiguration
  2. Nainstalujte balíček NuGet Microsoft.AspNetCore.Authentication.JwtBearer .
  3. Nakonfigurujte aplikaci tak, aby ověřila JWT pomocí balíčku NuGet a konfigurace prostředku Azure Communication Services. Potřebujete hodnoty audience, které jsou přítomné v datové části JWT.
  4. Ověřte vystavitele, cílovou skupinu a JWT.
    • Cílová skupina je ID prostředku služby Azure Communication Services, které jste použili k nastavení klienta automatizace volání. Informace o tom, jak ho získat, najdete tady .
    • Koncový bod sady webových klíčů JSON (JWKS) v konfiguraci OpenId obsahuje klíče použité k ověření JWT. Pokud je podpis platný a platnost tokenu nevypršela (do 5 minut od generování), klient může token použít k autorizaci.

Tento ukázkový kód ukazuje, jak použít Microsoft.IdentityModel.Protocols.OpenIdConnect k ověření payloadu webhooku.

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 implementation on the callback event
    return Results.Ok();
})
.RequireAuthorization()
.WithOpenApi();

app.UseAuthentication();
app.UseAuthorization();

app.Run();

Vylepšení zabezpečení zpětného volání webhooku pro automatizované hovory

Každé zpětné volání webhooku během hovoru odeslané službou Call Automation používá podepsaný webový token JSON (JWT) v hlavičce ověřování příchozího požadavku HTTPS. K zajištění integrity tokenu můžete použít standardní techniky ověřování Open ID Connect (OIDC) JWT, jak je uvedeno dále. Životnost JWT je pět (5) minut a pro každou událost odesílanou do identifikátoru URI zpětného volání se vytvoří nový token.

  1. Získejte adresu URL konfigurace Open ID: https://acscallautomation.communication.azure.com/calling/.well-known/acsopenidconfiguration
  2. Následující ukázka používá architekturu Spring vytvořenou pomocí spring initializru s Mavenem jako nástroj pro sestavení projektu.
  3. Do svého pom.xml přidejte následující závislosti:
  <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>
  1. Nakonfigurujte aplikaci tak, aby ověřila JWT a konfiguraci prostředku Azure Communication Services. Potřebujete hodnoty audience, které jsou přítomné v datové části JWT.
  2. Ověřte vystavitele, cílovou skupinu a JWT.
    • Cílová skupina je ID prostředku služby Azure Communication Services, které jste použili k nastavení klienta automatizace volání. Informace o tom, jak ho získat, najdete tady .
    • Koncový bod sady webových klíčů JSON (JWKS) v konfiguraci OpenId obsahuje klíče použité k ověření JWT. Pokud je podpis platný a platnost tokenu nevypršela (do 5 minut od generování), klient může token použít k autorizaci.

Tento ukázkový kód ukazuje, jak nakonfigurovat klienta OIDC tak, aby ověřil datovou část webhooku pomocí JWT.

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;
    }
}

Vylepšení zabezpečení zpětného volání webhooku pro automatizované hovory

Každé zpětné volání webhooku během hovoru odeslané službou Call Automation používá podepsaný webový token JSON (JWT) v hlavičce ověřování příchozího požadavku HTTPS. K zajištění integrity tokenu můžete použít standardní techniky ověřování Open ID Connect (OIDC) JWT, jak je uvedeno dále. Životnost JWT je pět (5) minut a pro každou událost odesílanou do identifikátoru URI zpětného volání se vytvoří nový token.

  1. Získejte adresu URL konfigurace Open ID: https://acscallautomation.communication.azure.com/calling/.well-known/acsopenidconfiguration
  2. Nainstalujte následující balíčky:
npm install express jwks-rsa jsonwebtoken
  1. Nakonfigurujte aplikaci tak, aby ověřila JWT a konfiguraci prostředku Azure Communication Services. Potřebujete hodnoty audience, které jsou přítomné v datové části JWT.
  2. Ověřte vystavitele, cílovou skupinu a JWT.
    • Cílová skupina je ID prostředku služby Azure Communication Services, které jste použili k nastavení klienta automatizace volání. Informace o tom, jak ho získat, najdete tady .
    • Koncový bod sady webových klíčů JSON (JWKS) v konfiguraci OpenId obsahuje klíče použité k ověření JWT. Pokud je podpis platný a platnost tokenu nevypršela (do 5 minut od generování), klient může token použít k autorizaci.

Tento ukázkový kód ukazuje, jak nakonfigurovat klienta OIDC tak, aby ověřil datovou část webhooku pomocí JWT.

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}`);
});

Vylepšení zabezpečení zpětného volání webhooku pro automatizované hovory

Každé zpětné volání webhooku během hovoru odeslané službou Call Automation používá podepsaný webový token JSON (JWT) v hlavičce ověřování příchozího požadavku HTTPS. K zajištění integrity tokenu můžete použít standardní techniky ověřování Open ID Connect (OIDC) JWT, jak je uvedeno dále. Životnost JWT je pět (5) minut a pro každou událost odesílanou do identifikátoru URI zpětného volání se vytvoří nový token.

  1. Získejte adresu URL konfigurace Open ID: https://acscallautomation.communication.azure.com/calling/.well-known/acsopenidconfiguration
  2. Nainstalujte následující balíčky:
pip install flask pyjwt
  1. Nakonfigurujte aplikaci tak, aby ověřila JWT a konfiguraci prostředku Azure Communication Services. Potřebujete hodnoty audience, které jsou přítomné v datové části JWT.
  2. Ověřte vystavitele, cílovou skupinu a JWT.
    • Cílová skupina je ID prostředku služby Azure Communication Services, které jste použili k nastavení klienta automatizace volání. Informace o tom, jak ho získat, najdete tady .
    • Koncový bod sady webových klíčů JSON (JWKS) v konfiguraci OpenId obsahuje klíče použité k ověření JWT. Pokud je podpis platný a platnost tokenu nevypršela (do 5 minut od generování), klient může token použít k autorizaci.

Tento ukázkový kód ukazuje, jak nakonfigurovat klienta OIDC tak, aby ověřil datovou část webhooku pomocí JWT.

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()

Události websockets služby Call Automation

Ověřovací token v hlavičce WebSocket

Každý požadavek na připojení WebSocket provedený službou Call Automation teď obsahuje podepsaný webový token JSON (JWT) v hlavičce Ověřování. Tento token lze ověřit pomocí standardních metod ověřování JWT (OpenID Connect).

  • JWT má životnost 24 hodin.
  • Pro každý požadavek na připojení k serveru WebSocket se vygeneruje nový token.
  • Další podrobnosti najdete v oficiální dokumentaci: Zabezpečený koncový bod webhooku – Azure Communication Services

Ukázka kódu Websocket

Tento ukázkový kód ukazuje, jak ověřovat požadavky na připojení WebSocket pomocí tokenů JSON Web Token (JWT).

// 1. Load OpenID Connect metadata
var configurationManager = new ConfigurationManager<OpenIdConnectConfiguration>(
    builder.Configuration["OpenIdConfigUrl"],
    new OpenIdConnectConfigurationRetriever());

var openIdConfig = await configurationManager.GetConfigurationAsync();
// 2. Register JWT authentication
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.Configuration = openIdConfig;
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidAudience = builder.Configuration["AllowedAudience"]
        };
    });

builder.Services.AddAuthorization();

var app = builder.Build();

// 3. Use authentication & authorization middleware
app.UseAuthentication();
app.UseAuthorization();

app.UseWebSockets();

// 4. WebSocket token validation manually in middleware
app.Use(async (context, next) =>
{
    if (context.Request.Path != "/ws")
    {
        await next(context);
        return;
    }

    if (!context.WebSockets.IsWebSocketRequest)
    {
        context.Response.StatusCode = StatusCodes.Status400BadRequest;
        await context.Response.WriteAsync("WebSocket connection expected.");
        return;
    }

    var result = await context.AuthenticateAsync();
    if (!result.Succeeded)
    {
        context.Response.StatusCode = StatusCodes.Status401Unauthorized;
        await context.Response.WriteAsync("Unauthorized WebSocket connection.");
        return;
    }

    context.User = result.Principal;

    // Optional: Log headers
    var correlationId = context.Request.Headers["x-ms-call-correlation-id"].FirstOrDefault();
    var callConnectionId = context.Request.Headers["x-ms-call-connection-id"].FirstOrDefault();

    Console.WriteLine($"Authenticated WebSocket - Correlation ID: {correlationId ?? "not provided"}");
    Console.WriteLine($"Authenticated WebSocket - CallConnection ID: {callConnectionId ?? "not provided"}");

    // Now you can safely accept the WebSocket and process the connection
    // var webSocket = await context.WebSockets.AcceptWebSocketAsync();
    // var mediaService = new AcsMediaStreamingHandler(webSocket, builder.Configuration);
    // await mediaService.ProcessWebSocketAsync();
});

Ukázka kódu Websocket

Tato ukázka ukazuje, jak nakonfigurovat klienta OpenID Connect (OIDC) k ověření požadavků připojení WebSocket pomocí webového tokenu JSON (JWT).

const audience = "ACS resource ID";
const issuer = "https://acscallautomation.communication.azure.com";

const jwksClient = new JwksClient({
  jwksUri: "https://acscallautomation.communication.azure.com/calling/keys",
});

wss.on("connection", async (ws, req) => {
  try {
    const authHeader = req.headers?.authorization || "";
    const token = authHeader.split(" ")[1];

    if (!token) {
      ws.close(1008, "Unauthorized");
      return;
    }

    verify(
      token,
      async (header, callback) => {
        try {
          const key = await jwksClient.getSigningKey(header.kid);
          const signingKey = key.getPublicKey();
          callback(null, signingKey);
        } catch (err) {
          callback(err);
        }
      },
      {
        audience,
        issuer,
        algorithms: ["RS256"],
      },
      (err, decoded) => {
        if (err) {
          console.error("WebSocket authentication failed:", err);
          ws.close(1008, "Unauthorized");
          return;
        }

        console.log(
          "Authenticated WebSocket connection with decoded JWT payload:",
          decoded
        );

        ws.on("message", async (message) => {
          // Process message
        });

        ws.on("close", () => {
          console.log("WebSocket connection closed");
        });
      }
    );
  } catch (err) {
    console.error("Unexpected error during WebSocket setup:", err);
    ws.close(1011, "Internal Server Error"); // 1011 = internal error
  }
});

Ukázka kódu Websocket

Tato ukázka ukazuje, jak nakonfigurovat klienta kompatibilního s protokolem OIDC tak, aby ověřil požadavky připojení WebSocket pomocí JWT.

Nezapomeňte nainstalovat požadovaný balíček: pip install cryptography

JWKS_URL = "https://acscallautomation.communication.azure.com/calling/keys"
ISSUER = "https://acscallautomation.communication.azure.com"
AUDIENCE = "ACS resource ID”

@app.websocket('/ws')
async def ws():
    try:
        auth_header = websocket.headers.get("Authorization")
        if not auth_header or not auth_header.startswith("Bearer "):
            await websocket.close(1008)  # Policy violation
            return

        token = auth_header.split()[1]

        jwks_client = PyJWKClient(JWKS_URL)
        signing_key = jwks_client.get_signing_key_from_jwt(token)

        decoded = jwt.decode(
            token,
            signing_key.key,
            algorithms=["RS256"],
            issuer=ISSUER,
            audience=AUDIENCE,
        )

        app.logger.info(f"Authenticated WebSocket connection with decoded JWT payload: {decoded}")
        await websocket.send("Connection authenticated.")

        while True:
            data = await websocket.receive()
            # Process incoming data

    except InvalidTokenError as e:
        app.logger.warning(f"Invalid token: {e}")
        await websocket.close(1008)
    except Exception as e:
        app.logger.error(f"Uncaught exception: {e}")
        await websocket.close(1011)  # Internal error

Rozsah IP adresy

Dalším způsobem, jak zabezpečit připojení websocket, je povolením pouze připojení Microsoft z určitých rozsahů IP adres.

Kategorie Rozsahy IP adres nebo plně kvalifikovaný název domény Přístavy
Volání automatizačního média 52.112.0.0/14, 52.122.0.0/15, 2603:1063::/38 UDP: 3478, 3479, 3480, 3481
Adresy URL zpětného volání služby Automation *.lync.com, *.teams.cloud.microsoft, *.teams.microsoft.com, teams.cloud.microsoft, teams.microsoft.com 52.112.0.0/14, 52.122.0.0/15, 2603:1027::/48, 2603:1037::/48, 2603:1047::/48, 2603:1057::/48, 2603:1063::/38, 2620:1ec:6::/48, 2620:1ec:40::/42 TCP: 443, 80 UDP: 443

Další kroky