Teilen über


Benutzerinteraktion in Durable Functions: Beispiel zur Telefonüberprüfung

In diesem Beispiel wird demonstriert, wie eine Durable Functions-Orchestrierung erstellt wird, die eine Benutzerinteraktion beinhaltet. Wenn eine Person in einen automatisierten Prozess einbezogen wird, muss der Prozess asynchron Benachrichtigungen an die Person senden und Antworten empfangen können. Zudem muss die Möglichkeit berücksichtigt werden, dass die Person nicht erreichbar ist. (In diesem letzten Teil gewinnen Zeitlimits an Bedeutung.)

In diesem Beispiel wird ein SMS-basiertes Telefonüberprüfungssystem implementiert. Diese Flusstypen werden häufig bei der Überprüfung der Telefonnummer eines Kunden oder bei der mehrstufigen Authentifizierung (Multi-Factor Authentication, MFA) verwendet. Es handelt sich um ein eindrucksvolles Beispiel, da die gesamte Implementierung über einige kleine Funktionen erfolgt. Es ist kein externer Datenspeicher, z.B. eine Datenbank, erforderlich.

Hinweis

Version 4 des Node.js-Programmiermodells für Azure Functions ist allgemein verfügbar. Das neue v4-Modell ist für eine flexiblere und intuitivere Benutzeroberfläche für JavaScript- und TypeScript-Entwickler konzipiert. Weitere Informationen zu den Unterschieden zwischen v3 und v4 finden Sie im Migrationshandbuch.

In den folgenden Codeschnipseln steht JavaScript (PM4) für das Programmiermodell V4, die neue Erfahrung.

Voraussetzungen

Übersicht über das Szenario

Bei der Telefonüberprüfung wird überprüft, ob Benutzer Ihrer Anwendung Spammer sind und ob die Angaben zu dieser Person korrekt sind. Die mehrstufige Authentifizierung wird häufig angewendet, um Benutzerkonten vor Hackern zu schützen. Die Herausforderung bei der Implementierung Ihrer eigenen Telefonüberprüfung besteht darin, dass eine zustandsbehaftete Interaktion mit einer Person erforderlich ist. In der Regel wird Code für einen Benutzer bereitgestellt (z.B. eine vierstellige Zahl). Dieser muss in einem angemessenen Zeitraum darauf reagieren.

Normale Azure-Funktionen sind zustandslos (wie viele andere Cloudendpunkte auf anderen Plattformen). Folglich umfassen diese Interaktionsarten explizit eine externe Verwaltung der Zustände in einer Datenbank oder einem anderen persistenten Speicher. Zusätzlich muss die Interaktion in mehrere Funktionen unterteilt werden, die zusammen koordiniert werden können. Sie benötigen beispielsweise mindestens eine Funktion, um sich für einen Code zu entscheiden, diesen an einer bestimmten Position persistent zu speichern und an das Telefon des Benutzers zu senden. Darüber hinaus ist mindestens eine weitere Funktion erforderlich, damit Sie eine Antwort vom Benutzer empfangen und diese wieder dem ursprünglichen Funktionsaufruf zuordnen können, um anschließend die Codevalidierung durchführen zu können. Ein Zeitlimit ist ein weiterer wichtiger Aspekt zur Gewährleistung der Sicherheit. Dieses Szenario kann schnell ziemlich komplex werden.

Mit Durable Functions wird die Komplexität dieses Szenarios erheblich reduziert. Wie Sie in diesem Beispiel sehen, kann eine Orchestratorfunktion die zustandsbehaftete Interaktion einfach und ohne die Einbeziehung von externen Datenspeichern verwalten. Da Orchestratorfunktionen dauerhaft sind, sind diese interaktiven Flüsse auch sehr zuverlässig.

Konfigurieren der Twilio-Integration

Dieses Beispiel beinhaltet die Verwendung des Twilio-Diensts zum Senden von SMS-Nachrichten an ein Mobiltelefon. Azure Functions bietet über die Twilio-Bindung bereits Unterstützung für Twilio an. Dieses Feature wird in dem Beispiel verwendet.

Zunächst benötigen Sie ein Twilio-Konto. Unter https://www.twilio.com/try-twilio können Sie kostenlos eines erstellen. Fügen Sie nach der Einrichtung Ihres Kontos die folgenden drei App-Einstellungen zu Ihrer Funktions-App hinzu.

Name der App-Einstellung Wertbeschreibung
TwilioAccountSid Die SID für Ihr Twilio-Konto
TwilioAuthToken Das Authentifizierungstoken für Ihr Twilio-Konto
TwilioPhoneNumber Die Ihrem Twilio-Konto zugeordnete Telefonnummer. Diese wird für das Senden von SMS-Nachrichten verwendet.

Die Funktionen

In diesem Artikel werden die folgenden Funktionen in der Beispiel-App schrittweise erläutert:

  • E4_SmsPhoneVerification: Eine Orchestratorfunktion, die den Telefonüberprüfungsprozess durchführt, einschließlich der Verwaltung von Timeouts und Wiederholungsversuchen.
  • E4_SendSmsChallenge: Eine Aktivitätsfunktion, die einen Code über eine SMS sendet.

Hinweis

Die Funktion HttpStart in Beispiel-App und Schnellstart fungiert als Orchestrierungsclient, der die Orchestratorfunktion auslöst.

Orchestratorfunktion „E4_SmsPhoneVerification“

[FunctionName("E4_SmsPhoneVerification")]
public static async Task<bool> Run(
    [OrchestrationTrigger] IDurableOrchestrationContext context)
{
    string phoneNumber = context.GetInput<string>();
    if (string.IsNullOrEmpty(phoneNumber))
    {
        throw new ArgumentNullException(
            nameof(phoneNumber),
            "A phone number input is required.");
    }

    int challengeCode = await context.CallActivityAsync<int>(
        "E4_SendSmsChallenge",
        phoneNumber);

    using (var timeoutCts = new CancellationTokenSource())
    {
        // The user has 90 seconds to respond with the code they received in the SMS message.
        DateTime expiration = context.CurrentUtcDateTime.AddSeconds(90);
        Task timeoutTask = context.CreateTimer(expiration, timeoutCts.Token);

        bool authorized = false;
        for (int retryCount = 0; retryCount <= 3; retryCount++)
        {
            Task<int> challengeResponseTask =
                context.WaitForExternalEvent<int>("SmsChallengeResponse");

            Task winner = await Task.WhenAny(challengeResponseTask, timeoutTask);
            if (winner == challengeResponseTask)
            {
                // We got back a response! Compare it to the challenge code.
                if (challengeResponseTask.Result == challengeCode)
                {
                    authorized = true;
                    break;
                }
            }
            else
            {
                // Timeout expired
                break;
            }
        }

        if (!timeoutTask.IsCompleted)
        {
            // All pending timers must be complete or canceled before the function exits.
            timeoutCts.Cancel();
        }

        return authorized;
    }
}

Hinweis

Auch wenn es auf den ersten Blick nicht offensichtlich ist: Dieser Orchestrator verstößt nicht gegen die Einschränkungen der deterministischen Orchestrierung. Sie ist deterministisch, weil die Eigenschaft CurrentUtcDateTime zur Berechnung der Zeit bis zum Ablauf des Timers verwendet wird und sie an diesem Punkt im Orchestratorcode bei jeder Wiedergabe den gleichen Wert zurückgibt. Dieses Verhalten ist wichtig, um sicherzustellen, dass jeder wiederholte Aufruf in Task.WhenAny den gleichen winner ergibt.

Nach dem Start führt diese Orchestratorfunktion Folgendes aus:

  1. Sie ruft eine Telefonnummer ab, an die die SMS-Benachrichtigung gesendet wird.
  2. Sie ruft E4_SendSmsChallenge auf, um eine SMS an den Benutzer zu senden, und gibt den erwarteten vierstelligen Abfragecode zurück.
  3. Sie erstellt einen permanenten Timer, der 90 Sekunden nach der aktuellen Uhrzeit ausgelöst wird.
  4. Parallel zum Timer wartet sie auf ein SmsChallengeResponse-Ereignis des Benutzers.

Der Benutzer erhält eine SMS-Nachricht mit einem vierstelligen Code. Zum Abschließen des Überprüfungsprozesses muss er diesen vierstelligen Code innerhalb von 90 Sekunden zurück an die Instanz der Orchestratorfunktion senden. Wenn er den falschen Code übergibt, hat er weitere drei Versuche, um den Code richtig einzugeben (innerhalb der 90 Sekunden).

Warnung

Es ist wichtig, Timer abzubrechen, wenn Sie diese nicht mehr benötigen, z.B. wenn wie im obigen Beispiel eine Abfragerückmeldung akzeptiert wurde.

Aktivitätsfunktion „E4_SendSmsChallenge“

Die Funktion E4_SendSmsChallenge sendet die SMS-Nachricht mit dem vierstelligen Code über die Twilio-Bindung an den Benutzer.

[FunctionName("E4_SendSmsChallenge")]
public static int SendSmsChallenge(
    [ActivityTrigger] string phoneNumber,
    ILogger log,
    [TwilioSms(AccountSidSetting = "TwilioAccountSid", AuthTokenSetting = "TwilioAuthToken", From = "%TwilioPhoneNumber%")]
        out CreateMessageOptions message)
{
    // Get a random number generator with a random seed (not time-based)
    var rand = new Random(Guid.NewGuid().GetHashCode());
    int challengeCode = rand.Next(10000);

    log.LogInformation($"Sending verification code {challengeCode} to {phoneNumber}.");

    message = new CreateMessageOptions(new PhoneNumber(phoneNumber));
    message.Body = $"Your verification code is {challengeCode:0000}";

    return challengeCode;
}

Hinweis

Sie müssen zuerst das Nuget-Paket Microsoft.Azure.WebJobs.Extensions.Twilio für Functions installieren, um den Beispielcode auszuführen. Installieren Sie nicht auch das NuGet-Twilio-Hauptpaket, da dies zu Versionsproblemen führen kann, die Buildfehler bewirken.

Ausführen des Beispiels

Wenn Sie die über HTTP ausgelösten Funktionen verwenden, die im Beispiel enthalten sind, können Sie mit der Orchestrierung beginnen, indem Sie folgende HTTP POST-Anforderung senden:

POST http://{host}/orchestrators/E4_SmsPhoneVerification
Content-Length: 14
Content-Type: application/json

"+1425XXXXXXX"
HTTP/1.1 202 Accepted
Content-Length: 695
Content-Type: application/json; charset=utf-8
Location: http://{host}/runtime/webhooks/durabletask/instances/741c65651d4c40cea29acdd5bb47baf1?taskHub=DurableFunctionsHub&connection=Storage&code={systemKey}

{"id":"741c65651d4c40cea29acdd5bb47baf1","statusQueryGetUri":"http://{host}/runtime/webhooks/durabletask/instances/741c65651d4c40cea29acdd5bb47baf1?taskHub=DurableFunctionsHub&connection=Storage&code={systemKey}","sendEventPostUri":"http://{host}/runtime/webhooks/durabletask/instances/741c65651d4c40cea29acdd5bb47baf1/raiseEvent/{eventName}?taskHub=DurableFunctionsHub&connection=Storage&code={systemKey}","terminatePostUri":"http://{host}/runtime/webhooks/durabletask/instances/741c65651d4c40cea29acdd5bb47baf1/terminate?reason={text}&taskHub=DurableFunctionsHub&connection=Storage&code={systemKey}"}

Die Orchestratorfunktion empfängt die angegebene Telefonnummer und sendet sofort eine SMS-Nachricht mit einem zufällig generierten vierstelligen Überprüfungscode an diese Nummer, z. B. 2168. Anschließend wartet die Funktion 90 Sekunden auf eine Antwort.

Für eine Beantwortung mit dem Code können Sie RaiseEventAsync (.NET) oder raiseEvent (JavaScript/TypeScript) in einer anderen Funktion verwenden oder den HTTP POST-Webhook sendEventPostUri aufrufen, auf den oben in der 202-Antwort verwiesen wird. Dabei wird {eventName} durch den Namen des Ereignisses, SmsChallengeResponse, ersetzt:

POST http://{host}/runtime/webhooks/durabletask/instances/741c65651d4c40cea29acdd5bb47baf1/raiseEvent/SmsChallengeResponse?taskHub=DurableFunctionsHub&connection=Storage&code={systemKey}
Content-Length: 4
Content-Type: application/json

2168

Wenn Sie dies vor Ablauf des Timers senden, ist die Orchestrierung abgeschlossen, und das Feld output wird auf true festgelegt, was auf eine erfolgreiche Überprüfung hinweist.

GET http://{host}/runtime/webhooks/durabletask/instances/741c65651d4c40cea29acdd5bb47baf1?taskHub=DurableFunctionsHub&connection=Storage&code={systemKey}
HTTP/1.1 200 OK
Content-Length: 144
Content-Type: application/json; charset=utf-8

{"runtimeStatus":"Completed","input":"+1425XXXXXXX","output":true,"createdTime":"2017-06-29T19:10:49Z","lastUpdatedTime":"2017-06-29T19:12:23Z"}

Wenn Sie den Timer ablaufen lassen oder viermal den falschen Code eingeben, können Sie den Status abfragen und die Ausgabe false der Orchestrierungsfunktion anzeigen, die angibt, dass die Telefonüberprüfung fehlgeschlagen ist.

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 145

{"runtimeStatus":"Completed","input":"+1425XXXXXXX","output":false,"createdTime":"2017-06-29T19:20:49Z","lastUpdatedTime":"2017-06-29T19:22:23Z"}

Nächste Schritte

In diesem Beispiel werden einige der erweiterten Funktionen von Durable Functions veranschaulicht, insbesondere WaitForExternalEvent- und CreateTimer-APIs. Sie haben gesehen, wie diese mit Task.WaitAny (C#)/context.df.Task.any (JavaScript/TypeScript)/context.task_any (Python) kombiniert werden können, um ein zuverlässiges Zeitlimitsystem zu implementieren. Dies ist bei der Interaktion mit realen Personen häufig hilfreich. Weitere Informationen zur Verwendung von Durable Functions finden Sie in einer Reihe von Artikeln, die detaillierte Erläuterungen zu bestimmten Themen bieten.