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:
- Sie ruft eine Telefonnummer ab, an die die SMS-Benachrichtigung gesendet wird.
- Sie ruft E4_SendSmsChallenge auf, um eine SMS an den Benutzer zu senden, und gibt den erwarteten vierstelligen Abfragecode zurück.
- Sie erstellt einen permanenten Timer, der 90 Sekunden nach der aktuellen Uhrzeit ausgelöst wird.
- 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.