Anwendungsfälle für die Zugriffskontrolle zur Sicherung von Anfragen und Antworten - AWS AppSync

Die vorliegende Übersetzung wurde maschinell erstellt. Im Falle eines Konflikts oder eines Widerspruchs zwischen dieser übersetzten Fassung und der englischen Fassung (einschließlich infolge von Verzögerungen bei der Übersetzung) ist die englische Fassung maßgeblich.

Anwendungsfälle für die Zugriffskontrolle zur Sicherung von Anfragen und Antworten

Im Abschnitt Sicherheit haben Sie sich mit den verschiedenen Autorisierungsmodi für Ihren Schutz vertraut gemacht. API Außerdem wurde eine Einführung in detaillierte Autorisierungsmechanismen gegeben, um die Konzepte und den Ablauf zu verstehen. Da Sie mithilfe von GraphQL Resolver Mapping-Vorlagen logische Operationen an Daten ausführen AWS AppSync können, können Sie Daten beim Lesen oder Schreiben auf sehr flexible Weise schützen, indem Sie eine Kombination aus Benutzeridentität, Bedingungen und Dateneinspeisung verwenden.

Wenn Sie mit der Bearbeitung von AWS AppSync Resolvern nicht vertraut sind, lesen Sie das Programmierhandbuch.

Übersicht

Die Gewährung des Zugriffs auf Daten in einem System erfolgt traditionell über eine Zugriffskontrollmatrix, bei der sich die gewährten Berechtigungen aus einer Zeile (Ressource) und einer Spalte (Benutzer/Rolle) überschneiden.

AWS AppSync verwendet Ressourcen in Ihrem eigenen Konto und überträgt Identitätsinformationen (Benutzer/Rolle) in die GraphQL-Anfrage und -Antwort als Kontextobjekt, das Sie im Resolver verwenden können. Das bedeutet, dass Berechtigungen basierend auf der Resolver-Logik entweder für Schreib- oder Lesevorgänge entsprechend gewährt werden können. Befindet sich diese Logik auf Ressourcenebene, sodass beispielsweise nur bestimmte benannte Benutzer oder Gruppen in eine bestimmte Datenbankzeile lesen/schreiben können, müssen diese „Autorisierungsmetadaten“ gespeichert werden. AWS AppSync speichert keine Daten, daher müssen Sie diese Autorisierungsmetadaten zusammen mit den Ressourcen speichern, damit die Berechtigungen berechnet werden können. Autorisierungsmetadaten stellen in der Regel ein Attribut (Spalte) in einer DynamoDB-Tabelle dar, wie z. B. ein Owner oder eine Liste von Benutzern/Gruppen. Es können zum Beispiel die Attribute Readers und Writers vorliegen.

Im Allgemeinen bedeutet dies, dass Sie beim Lesen eines einzelnen Elements aus einer Datenquelle eine bedingte #if () ... #end-Anweisung in der Antwortvorlage ausführen, nachdem der Resolver den Lesevorgang aus der Datenquelle beendet hat. Bei der Prüfung werden normalerweise Benutzer- oder Gruppenwerte in $context.identity zur Prüfung der Mitgliedschaft anhand der Autorisierungsmetadaten verwendet, die von einem Lesevorgang zurückgegeben werden. Für mehrere Datensätze, wie z B. aus einer Tabelle Scan oder Query zurückgegebene Listen, senden Sie die Bedingungsprüfung mithilfe ähnlicher Benutzer- oder Gruppenwerte als Teil der Operation an die Datenquelle.

Entsprechend wenden Sie beim Schreiben von Daten eine bedingte Anweisung auf die Aktion an (z. B. PutItem oder UpdateItem), um zu sehen, ob der Benutzer oder die Gruppe, der bzw. die eine Mutation vornimmt, dazu berechtigt ist. Die Bedingung verwendet häufig einen Wert in $context.identity zum Vergleich mit den Autorisierungsmetadaten dieser Ressource. Für beide Anforderungs- und Antwortvorlagen können Sie benutzerdefinierte Header von Clients verwenden, um Validierungsprüfungen durchzuführen.

Lesen von Daten

Wie oben erläutert, müssen die Autorisierungsmetadaten zum Durchführen einer Prüfung mit einer Ressource gespeichert oder an die GraphQL-Anforderung (Identität, Header usw.) weitergegeben werden. Um dies zu veranschaulichen, nehmen wir an, Sie haben die unten angegebene DynamoDB-Tabelle vorliegen:

DynamoDB table with ID, Data, PeopleCanAccess, GroupsCanAccess, and Owner columns.

Der Primärschlüssel ist id und die Daten, auf die zugegriffen werden soll, entsprechen Data. Die anderen Spalten sind Beispiele für Prüfungen, die Sie für die Autorisierung durchführen können. Ownerwürde eine String Weile dauern PeopleCanAccess und GroupsCanAccess wäre String Sets so, wie in der Referenz zur Resolver-Mapping-Vorlage für DynamoDB beschrieben.

Das Diagramm in der Übersicht über die Resolver-Zuweisungsvorlage zeigt, wie die Antwortvorlage nicht nur das Kontextobjekt, sondern auch die Ergebnisse aus der Datenquelle enthält. Für GraphQL-Abfragen einzelner Elemente können Sie anhand der Antwortvorlage prüfen, ob der Benutzer diese Ergebnisse sehen darf, oder eine Autorisierungsfehlermeldung zurückgeben. Dies wird manchmal auch als „Autorisierungsfilter“ bezeichnet. Bei GraphQL-Abfragen, die Listen zurückgeben, ist es bei Verwendung eines Scans oder einer Abfrage effizienter, die Prüfung für die Anforderungsvorlage durchzuführen und Daten nur zurückzugeben, wenn eine Autorisierungsbedingung erfüllt ist. Die Implementierung lautet wie folgt:

  1. GetItem - Autorisierungsprüfung für einzelne Datensätze. Dieser Vorgang wird mit #if() ... #end-Anweisungen ausgeführt.

  2. Scan-/Abfrageoperationen – Die Autorisierungsprüfung besteht aus einer "filter":{"expression":...}-Anweisung. Gängige Prüfungen erfolgen auf Gleichheit (attribute = :input) oder darauf, ob ein Wert in einer Liste (contains(attribute, :input)) enthalten ist.

Im zweiten Fall steht attribute in beiden Anweisungen für den Spaltennamen des Datensatzes in einer Tabelle, wie z. B. Owner in unserem Beispiel oben. Sie können diesen Wert mit einem #-Zeichen versehen und "expressionNames":{...} verwenden. Dies ist aber nicht obligatorisch. :input ist eine Referenz auf den Wert, den Sie mit dem Datenbankattribut vergleichen möchten, das Sie in "expressionValues":{...} definieren. Diese Beispiele finden Sie weiter unten.

Anwendungsfall: Besitzer kann lesen

Wenn Sie unter Verwendung der Tabelle oben nur Daten zurückgegeben möchten, wenn Owner == Nadia für einen einzelnen Lesevorgang (GetItem) gilt, sieht Ihre Vorlage wie folgt aus:

#if($context.result["Owner"] == $context.identity.username) $utils.toJson($context.result) #else $utils.unauthorized() #end

An dieser Stelle möchten wir auf einige Punkte hinweisen, die für die verbleibenden Abschnitte relevant sind. Bei der Prüfung wird zunächst der benutzerfreundliche Anmeldename verwendet$context.identity.username, wenn der Amazon Cognito-Benutzerpool verwendet wird, und der Nutzeridentität, falls verwendet IAM wird (einschließlich Amazon Cognito Federated Identities). Es gibt noch andere Werte, die für einen Besitzer gespeichert werden müssen, z. B. den eindeutigen Wert „Amazon Cognito Identity“, der nützlich ist, wenn Anmeldungen von mehreren Standorten aus zusammengeführt werden. Sie sollten sich die Optionen ansehen, die in der Resolver Mapping Template Context Reference verfügbar sind.

Zweitens $util.unauthorized() ist die bedingte Else-Überprüfung, mit der Sie antworten, völlig optional, wird aber als bewährte Methode beim Entwerfen Ihres GraphQL API empfohlen.

Anwendungsfall: Hardcode-spezifischer Zugriff

// This checks if the user is part of the Admin group and makes the call #foreach($group in $context.identity.claims.get("cognito:groups")) #if($group == "Admin") #set($inCognitoGroup = true) #end #end #if($inCognitoGroup) { "version" : "2017-02-28", "operation" : "UpdateItem", "key" : { "id" : $util.dynamodb.toDynamoDBJson($ctx.args.id) }, "attributeValues" : { "owner" : $util.dynamodb.toDynamoDBJson($context.identity.username) #foreach( $entry in $context.arguments.entrySet() ) ,"${entry.key}" : $util.dynamodb.toDynamoDBJson($entry.value) #end } } #else $utils.unauthorized() #end

Anwendungsfall: Filtern einer Ergebnisliste

Im vorherigen Beispiel haben Sie eine Prüfung für $context.result direkt durchführen können, da ein einzelnes Element zurückgegeben wurde. Bei einigen Operationen wie z. B. einem Scan werden jedoch mehrere Elemente in $context.result.items zurückgegeben. In diesen Fällen müssen Sie den Autorisierungsfilter anwenden, sodass nur Ergebnisse erscheinen, die der Benutzer sehen darf. Angenommen, für das Owner Feld war dieses Mal die Amazon Cognito IdentityID für den Datensatz festgelegt. Sie könnten dann die folgende Antwortzuordnungsvorlage verwenden, um zu filtern, dass nur die Datensätze angezeigt werden, die dem Benutzer gehörten:

#set($myResults = []) #foreach($item in $context.result.items) ##For userpools use $context.identity.username instead #if($item.Owner == $context.identity.cognitoIdentityId) #set($added = $myResults.add($item)) #end #end $utils.toJson($myResults)

Anwendungsfall: mehrere Personen können lesen

Eine weitere gängige Autorisierungsoption besteht darin, einer Gruppe von Benutzern das Lesen der Daten zu erlauben. Im folgenden Beispiel gibt "filter":{"expression":...} nur die Werte aus einem Tabellenscan zurück, wenn der Benutzer, der die GraphQL-Abfrage ausführt, im Set für PeopleCanAccess aufgelistet wird.

{ "version" : "2017-02-28", "operation" : "Scan", "limit": #if(${context.arguments.count}) $util.toJson($context.arguments.count) #else 20 #end, "nextToken": #if(${context.arguments.nextToken}) $util.toJson($context.arguments.nextToken) #else null #end, "filter":{ "expression": "contains(#peopleCanAccess, :value)", "expressionNames": { "#peopleCanAccess": "peopleCanAccess" }, "expressionValues": { ":value": $util.dynamodb.toDynamoDBJson($context.identity.username) } } }

Anwendungsfall: Gruppe kann lesen

Ähnlich wie im letzten Anwendungsfall kann es vorkommen, dass nur Benutzer in einer oder mehreren Gruppen Leserechte für bestimmte Elemente in einer Datenbank besitzen. Die Verwendung der "expression": "contains()"-Operation ist zwar ähnlich, es muss in der Gruppenmitgliedschaft jedoch ein logisches ODER zwischen allen Gruppen, denen der Benutzer angehören kann, berücksichtigt werden. In diesem Fall erstellen wir eine $expression-Anweisung unten für jede Gruppe, in der sich der Benutzer befindet, und übergeben diese an den Filter:

#set($expression = "") #set($expressionValues = {}) #foreach($group in $context.identity.claims.get("cognito:groups")) #set( $expression = "${expression} contains(groupsCanAccess, :var$foreach.count )" ) #set( $val = {}) #set( $test = $val.put("S", $group)) #set( $values = $expressionValues.put(":var$foreach.count", $val)) #if ( $foreach.hasNext ) #set( $expression = "${expression} OR" ) #end #end { "version" : "2017-02-28", "operation" : "Scan", "limit": #if(${context.arguments.count}) $util.toJson($context.arguments.count) #else 20 #end, "nextToken": #if(${context.arguments.nextToken}) $util.toJson($context.arguments.nextToken) #else null #end, "filter":{ "expression": "$expression", "expressionValues": $utils.toJson($expressionValues) } }

Daten schreiben

Das Schreiben von Daten für Mutationen wird immer in der Anforderungszuweisungsvorlage gesteuert. Im Fall von DynamoDB-Datenquellen besteht der Schlüssel in der Verwendung einer entsprechenden "condition":{"expression"...}" zur Durchführung der Validierung anhand der Autorisierungsmetadaten in dieser Tabelle. Unter Sicherheit befindet sich ein Beispiel, das zum Prüfen des Felds Author in einer Tabelle verwendet werden kann. Die Anwendungsfälle in diesem Abschnitt veranschaulichen weitere Fälle dieser Art.

Anwendungsfall: mehrere Besitzer

Aus dem bereits verwendeten Beispieltabellendiagramm wird die PeopleCanAccess-Liste vorausgesetzt.

{ "version" : "2017-02-28", "operation" : "UpdateItem", "key" : { "id" : $util.dynamodb.toDynamoDBJson($ctx.args.id) }, "update" : { "expression" : "SET meta = :meta", "expressionValues": { ":meta" : $util.dynamodb.toDynamoDBJson($ctx.args.meta) } }, "condition" : { "expression" : "contains(Owner,:expectedOwner)", "expressionValues" : { ":expectedOwner" : $util.dynamodb.toDynamoDBJson($context.identity.username) } } }

Anwendungsfall: Die Gruppe kann einen neuen Datensatz erstellen

#set($expression = "") #set($expressionValues = {}) #foreach($group in $context.identity.claims.get("cognito:groups")) #set( $expression = "${expression} contains(groupsCanAccess, :var$foreach.count )" ) #set( $val = {}) #set( $test = $val.put("S", $group)) #set( $values = $expressionValues.put(":var$foreach.count", $val)) #if ( $foreach.hasNext ) #set( $expression = "${expression} OR" ) #end #end { "version" : "2017-02-28", "operation" : "PutItem", "key" : { ## If your table's hash key is not named 'id', update it here. ** "id" : $util.dynamodb.toDynamoDBJson($ctx.args.id) ## If your table has a sort key, add it as an item here. ** }, "attributeValues" : { ## Add an item for each field you would like to store to Amazon DynamoDB. ** "title" : $util.dynamodb.toDynamoDBJson($ctx.args.title), "content": $util.dynamodb.toDynamoDBJson($ctx.args.content), "owner": $util.dynamodb.toDynamoDBJson($context.identity.username) }, "condition" : { "expression": $util.toJson("attribute_not_exists(id) AND $expression"), "expressionValues": $utils.toJson($expressionValues) } }

Anwendungsfall: Die Gruppe kann einen vorhandenen Datensatz aktualisieren

#set($expression = "") #set($expressionValues = {}) #foreach($group in $context.identity.claims.get("cognito:groups")) #set( $expression = "${expression} contains(groupsCanAccess, :var$foreach.count )" ) #set( $val = {}) #set( $test = $val.put("S", $group)) #set( $values = $expressionValues.put(":var$foreach.count", $val)) #if ( $foreach.hasNext ) #set( $expression = "${expression} OR" ) #end #end { "version" : "2017-02-28", "operation" : "UpdateItem", "key" : { "id" : $util.dynamodb.toDynamoDBJson($ctx.args.id) }, "update":{ "expression" : "SET title = :title, content = :content", "expressionValues": { ":title" : $util.dynamodb.toDynamoDBJson($ctx.args.title), ":content" : $util.dynamodb.toDynamoDBJson($ctx.args.content) } }, "condition" : { "expression": $util.toJson($expression), "expressionValues": $utils.toJson($expressionValues) } }

Öffentliche und private Aufzeichnungen

Mit den bedingten Filtern können Sie Daten auch als privat, öffentlich oder mit einer anderen booleschen Prüfung kennzeichnen. Diese Vorgehensweise lässt sich mit einem Autorisierungsfilter innerhalb der Antwortvorlage kombinieren. Mit dieser Prüfung können Daten vorübergehend ausgeblendet oder aus der Anzeige entfernt werden, ohne dass die Gruppenmitgliedschaft kontrolliert werden muss.

Nehmen wir beispielsweise an, Sie haben ein Attribut für jedes Element in Ihrer DynamoDB-Tabelle namens public entweder mit dem Wert yes oder no hinzugefügt. Die folgende Antwortvorlage könnte bei einem GetItem Anruf verwendet werden, um Daten nur anzuzeigen, wenn sich der Benutzer in einer Gruppe befindet, die Zugriff darauf hat, AND wenn diese Daten als öffentlich markiert sind:

#set($permissions = $context.result.GroupsCanAccess) #set($claimPermissions = $context.identity.claims.get("cognito:groups")) #foreach($per in $permissions) #foreach($cgroups in $claimPermissions) #if($cgroups == $per) #set($hasPermission = true) #end #end #end #if($hasPermission && $context.result.public == 'yes') $utils.toJson($context.result) #else $utils.unauthorized() #end

Der obige Code kann auch ein logisches ODER (||) verwenden, um Benutzern das Lesen zu erlauben, wenn sie über die Berechtigung für einen Datensatz verfügen oder dieser öffentlich ist:

#if($hasPermission || $context.result.public == 'yes') $utils.toJson($context.result) #else $utils.unauthorized() #end

Im Allgemeinen sind die Standardoperatoren ==!=, && und || beim Ausführen der Autorisierungsprüfungen nützlich.

Daten in Echtzeit

Sie können eine differenzierte Zugriffskontrolle auf GraphQL-Abonnements zu dem Zeitpunkt anwenden, zu dem ein Client ein Abonnement abschließt. Verwenden Sie dazu die gleichen Techniken, wie weiter oben in dieser Dokumentation beschrieben. Sie fügen dem Abonnementfeld einen Resolver an. An diesem Punkt können Sie Daten aus einer Datenquelle abfragen und eine bedingte Logik entweder in der Anforderungs- oder der Antwortzuweisungsvorlage ausführen. Sie können auch zusätzliche Daten an den Client zurückgeben, z. B. die ersten Ergebnisse aus einem Abonnement, solange die Datenstruktur der Struktur des zurückgegebenen Typs in Ihrem GraphQL-Abonnement entspricht.

Anwendungsfall: Der Benutzer kann nur bestimmte Konversationen abonnieren

Ein häufiger Anwendungsfall für Echtzeitdaten mit GraphQL-Abonnements ist die Erstellung einer Messaging-App oder einer Anwendung für private Chats. Beim Erstellen einer Chat-Anwendung, die von mehreren Benutzern verwendet wird, können Unterhaltungen zwischen zwei Personen oder zwischen mehreren Personen stattfinden. Diese können in „Räume“ gruppiert werden, die privat oder öffentlich sind. Daher soll nur ein Benutzer autorisiert werden, um eine Unterhaltung zu abonnieren (die zwischen zwei Personen oder in einer Gruppe stattfinden kann), für die ihnen Zugriff gewährt wurde. Zu Demonstrationszwecken wird im Beispiel unten ein einfacher Anwendungsfall von einem Benutzer gezeigt, der eine privaten Nachricht an einen anderen Benutzer sendet. Das Setup umfasst zwei Amazon DynamoDB-Tabellen:

  • Nachrichtentabelle: (Primärschlüssel) toUser, (Sortierschlüssel) id

  • Berechtigungstabelle: (Primärschlüssel) username

Die Nachrichtentabelle speichert die tatsächlichen Nachrichten, die über eine GraphQL-Mutation gesendet werden. Die Berechtigungstabelle wird vom GraphQL-Abonnement im Hinblick auf die Autorisierung zur Client-Verbindungszeit geprüft. Im folgenden Beispiel wird davon ausgegangen, dass Sie das folgende GraphQL-Schema verwenden:

input CreateUserPermissionsInput { user: String! isAuthorizedForSubscriptions: Boolean } type Message { id: ID toUser: String fromUser: String content: String } type MessageConnection { items: [Message] nextToken: String } type Mutation { sendMessage(toUser: String!, content: String!): Message createUserPermissions(input: CreateUserPermissionsInput!): UserPermissions updateUserPermissions(input: UpdateUserPermissionInput!): UserPermissions } type Query { getMyMessages(first: Int, after: String): MessageConnection getUserPermissions(user: String!): UserPermissions } type Subscription { newMessage(toUser: String!): Message @aws_subscribe(mutations: ["sendMessage"]) } input UpdateUserPermissionInput { user: String! isAuthorizedForSubscriptions: Boolean } type UserPermissions { user: String isAuthorizedForSubscriptions: Boolean } schema { query: Query mutation: Mutation subscription: Subscription }

Einige der Standardoperationen, wie z. B., werden im Folgenden nicht behandeltcreateUserPermissions(), um die Abonnement-Resolver zu veranschaulichen, sondern sind Standardimplementierungen von DynamoDB-Resolvern. Stattdessen konzentrieren wir uns auf Autorisierungsabläufe für Abonnements mit Resolvern. Zum Senden einer Nachricht von einem Benutzer an einen anderen fügen Sie dem sendMessage()-Feld einen Resolver an und wählen die Nachrichten-Tabelle als Datenquelle mit der folgenden Anforderungsvorlage aus:

{ "version" : "2017-02-28", "operation" : "PutItem", "key" : { "toUser" : $util.dynamodb.toDynamoDBJson($ctx.args.toUser), "id" : $util.dynamodb.toDynamoDBJson($util.autoId()) }, "attributeValues" : { "fromUser" : $util.dynamodb.toDynamoDBJson($context.identity.username), "content" : $util.dynamodb.toDynamoDBJson($ctx.args.content), } }

In diesem Beispiel verwenden wir $context.identity.username. Dadurch werden Benutzerinformationen für AWS Identity and Access Management unsere Amazon Cognito Cognito-Benutzer zurückgegeben. Die Antwortvorlage ist ein einfacher Pass-Through von $util.toJson($ctx.result). Speichern Sie die Daten und wechseln Sie wieder zur Seite "Schema". Fügen Sie anschließend einen Resolver für das newMessage()-Abonnement mit der Tabelle Berechtigungen als Datenquelle und der folgenden Anforderungszuweisungsvorlage an:

{ "version": "2018-05-29", "operation": "GetItem", "key": { "username": $util.dynamodb.toDynamoDBJson($ctx.identity.username), }, }

Führen Sie mit der folgenden Antwortzuweisungsvorlage die Autorisierungsprüfungen anhand der Daten aus der Tabelle Berechtigungen durch:

#if(! ${context.result}) $utils.unauthorized() #elseif(${context.identity.username} != ${context.arguments.toUser}) $utils.unauthorized() #elseif(! ${context.result.isAuthorizedForSubscriptions}) $utils.unauthorized() #else ##User is authorized, but we return null to continue null #end

In diesem Fall nehmen Sie drei Autorisierungsprüfungen vor. Die erste stellt sicher, dass ein Ergebnis zurückgegeben wird. Die zweite stellt sicher, dass der Benutzer keine Nachrichten abonniert, die für eine andere Person gedacht sind. Die dritte Option stellt sicher, dass der Benutzer beliebige Felder abonnieren darf, indem ein DynamoDB-Attribut als isAuthorizedForSubscriptions gespeichert überprüft wird. BOOL

Um die Dinge zu testen, könnten Sie sich mit Amazon Cognito Cognito-Benutzerpools und einem Benutzer namens „Nadia“ bei der AWS AppSync Konsole anmelden und dann das folgende GraphQL-Abonnement ausführen:

subscription AuthorizedSubscription { newMessage(toUser: "Nadia") { id toUser fromUser content } }

Wenn in der Tabelle Permissions (Berechtigungen) ein Datensatz für das username-Schlüsselattribut Nadia enthalten ist, wobei isAuthorizedForSubscriptions auf true gesetzt ist, sehen Sie eine erfolgreiche Antwort. Wenn Sie einen anderen username in der newMessage()-Abfrage oben angeben, wird ein Fehler zurückgegeben.