API-Routen mit einem JWT-Authorizer schützen

AWSBeginner
Jetzt üben

Einführung

Die Bestell-API muss für Lese- und Schreibvorgänge eine Anmeldung verlangen und die Zustandsroute öffentlich lassen. Du fügst Tokenprüfungen zu beiden Bestellrouten hinzu, testest angenommene und abgelehnte Anfragen und bestätigst, dass abgelehnte Aufrufe Geschäftsdaten unverändert lassen.

Schließe zuerst Die Anwendungsanmeldung mit Cognito hinzufügen und Eine Bestell-API bauen und validieren ab. Diese neue Umgebung stellt ein funktionierendes öffentliches Backend und synthetische Identitätsfixtures bereit; frühere Ressourcen und Token werden nicht wiederverwendet.

Bezug zu Zertifizierungen

Dieses Lab bietet praktische Übungen zu den folgenden Prüfungsthemen.

Die öffentlichen Routen prüfen und anmelden

In diesem Schritt stellst du den aktuellen öffentlichen Routenzustand fest und erhältst ein echtes Zugriffstoken für den vorgesehenen App-Client.

Wechsle in den Arbeitsbereich und halte neu erstellte Tokendateien privat:

cd /home/labex/project
umask 077

Öffne AWS View neben Terminal, um API-Anfragen, Funktionsausführungen und gespeicherte Bestellungen zu vergleichen. Gateway-Anfrage-Logs und Lambda-Ausführungslogs sind getrennt: Ein abgelehntes Token sollte gestoppt werden, bevor die Funktion ausgeführt wird.

Lies den sicheren Fixture-Bestand. Er identifiziert den vorgesehenen Pool und Client, einen anderen Client, einen anderen Aussteller und den unbeteiligten Referenzpool:

cat scenario.json

Verwende jq -r, um die Haupt-IDs auszuwählen, und speichere die Ausgabe mit der Befehlssubstitution $(...):

POOL_ID=$(jq -r '.main.pool' scenario.json)
CLIENT_ID=$(jq -r '.main.client' scenario.json)

Finde die vorbereitete HTTP-API anhand ihres Namens und halte ihre erzeugte ID für die Bereinigung fest:

API_ID=$(aws apigatewayv2 get-apis \
  --query "Items[?Name=='labex-a05'].ApiId | [0]" \
  --output text)
printf '%s\n' "$API_ID" > api-id.txt

Prüfe die Autorisierungstypen der Routen:

aws apigatewayv2 get-routes \
  --api-id "$API_ID" \
  --query 'Items[].{Route:RouteKey,Authorization:AuthorizationType}'

Alle drei Routen verwenden anfangs NONE. Das Benutzerverzeichnis allein hat die API nicht geschützt. Bilde ihre Arbeitsbereichsadresse:

API_URL="http://127.0.0.1:8081/api/$API_ID"
curl -i "$API_URL/health"

Erwarte HTTP 200 und healthy: true, mit einer tatsächlichen Zustandsausführung in AWS View und ohne Bestellungen.

Melde den vorbereiteten synthetischen Benutzer über den vorgesehenen öffentlichen Client an und speichere die echte Antwort privat:

aws cognito-idp initiate-auth \
  --client-id "$CLIENT_ID" \
  --auth-flow USER_PASSWORD_AUTH \
  --auth-parameters file://sign-in.json \
  --query AuthenticationResult \
  --output json > tokens.json

Extrahiere Zugriffs- und ID-Token, ohne sie auszugeben:

jq -r '.AccessToken' tokens.json > access-token.txt
jq -r '.IdToken' tokens.json > id-token.txt

Verwende nur das Zugriffstoken, um den aktuellen Anwendungsbenutzernamen zu lesen:

aws cognito-idp get-user \
  --access-token "$(cat access-token.txt)" \
  --query Username \
  --output text

Erwarte labex-demo. AWS View zeigt den vorbereiteten bestätigten Benutzer und Client. Die unabhängige Prüfung validiert das tatsächlich ausgegebene Token und die aktuelle Identität; sie authentifiziert nicht erneut für dich.

Den Authorizer für beide Bestellrouten verlangen

In diesem Schritt konfigurierst du den vorgesehenen Aussteller und Empfänger und wendest denselben JWT-Authorizer auf private Lese- und Schreibvorgänge an.

Ein JWT-Authorizer prüft ein signiertes Token, bevor API Gateway eine geschützte Route aufruft. Der Aussteller ist die signierte Cognito-Pool-Identität. Der Empfänger ist der vorgesehene App-Client: API Gateway validiert aud, wenn vorhanden, andernfalls client_id. Bilde den Aussteller aus der tatsächlichen Pool-ID:

ISSUER="https://cognito-idp.us-east-1.amazonaws.com/$POOL_ID"

Verwende eine Here-Dokument-Markierung ohne Anführungszeichen, damit die Shell die beiden IDs in eine normale Konfigurationsdatei einsetzt:

cat > jwt-config.json <<JSON
{
  "Issuer": "$ISSUER",
  "Audience": ["$CLIENT_ID"]
}
JSON

Erstelle einen JWT-Authorizer. Einfache Anführungszeichen erhalten die wörtliche Identitätsquelle $request.header.Authorization, statt sie als Shellvariable zu ersetzen:

AUTH_ID=$(aws apigatewayv2 create-authorizer \
  --api-id "$API_ID" \
  --name OrdersUsers \
  --authorizer-type JWT \
  --identity-source '$request.header.Authorization' \
  --jwt-configuration file://jwt-config.json \
  --query AuthorizerId \
  --output text)

Finde die erzeugte ID jeder Bestellroute:

READ_ROUTE_ID=$(aws apigatewayv2 get-routes \
  --api-id "$API_ID" \
  --query "Items[?RouteKey=='GET /orders'].RouteId | [0]" \
  --output text)
WRITE_ROUTE_ID=$(aws apigatewayv2 get-routes \
  --api-id "$API_ID" \
  --query "Items[?RouteKey=='POST /orders'].RouteId | [0]" \
  --output text)

API Gateway unterscheidet Zugriffstoken nicht universell von ID-Token. Diese nativen Zugriffstoken aus dem Cognito-Passwortablauf enthalten aws.cognito.signin.user.admin; das Verlangen dieses Scopes lehnt das ID-Token ab, dem er fehlt. Der Scope bezeichnet Cognito-Benutzer-Self-Service-Zugriff, nicht AWS-Administration oder Bestellbesitz. Diese Übung erstellt keinen eigenen Geschäfts-Scope.

Verlange JWT und diesen Scope für die Leseroute:

aws apigatewayv2 update-route \
  --api-id "$API_ID" \
  --route-id "$READ_ROUTE_ID" \
  --authorization-type JWT \
  --authorizer-id "$AUTH_ID" \
  --authorization-scopes aws.cognito.signin.user.admin \
  --query '{Route:RouteKey,Authorization:AuthorizationType,Scopes:AuthorizationScopes}'

Wende dieselbe Anforderung auf die Schreibroute an:

aws apigatewayv2 update-route \
  --api-id "$API_ID" \
  --route-id "$WRITE_ROUTE_ID" \
  --authorization-type JWT \
  --authorizer-id "$AUTH_ID" \
  --authorization-scopes aws.cognito.signin.user.admin \
  --query '{Route:RouteKey,Authorization:AuthorizationType,Scopes:AuthorizationScopes}'

Die vorbereitete Stage $default verwendet AutoDeploy, sodass die Routenänderungen automatisch angewendet werden. Lies alle Routentypen erneut:

aws apigatewayv2 get-routes \
  --api-id "$API_ID" \
  --query 'Items[].{Route:RouteKey,Authorization:AuthorizationType}'

Erwarte JWT auf beiden Bestellrouten und NONE auf GET /health. Vergleiche in AWS View den tatsächlichen Aussteller, vorgesehenen Empfänger, die Authorizer-IDs der Routen und den erforderlichen Scope. Einen Authorizer lediglich zu erstellen, ohne ihn jeder privaten Route zuzuordnen, schützt diese Routen nicht.

Beispiel in AWS View mit öffentlicher Zustandsroute und JWT-geschützten Bestellrouten

Beispiel: Beide Bestellrouten teilen den vorgesehenen Authorizer und Scope, während die Zustandsroute öffentlich bleibt. Erzeugte Ressourcen-IDs unterscheiden sich in deiner VM.

Der Authorizer nimmt die private Anfrage an oder lehnt sie ab, bevor der Handler ausgeführt wird.

Angenommene und abgelehnte Anfragen testen

In diesem Schritt belegst du tatsächlichen Geschäftszugriff für den vorgesehenen Benutzer und die Ablehnung vor Lambda bei fehlenden oder ungeeigneten Token.

Ein Bearer-Token authentifiziert jeden, der es vorlegt. Lies es aus der privaten Transportdatei innerhalb des Authorization-Headers. Verwende weiterhin curl -i, um nur Antwortheader und -inhalt zu zeigen; verwende keine ausführliche Protokollierung von Anfrageheadern. Sende eine gültige Bestellung mit Menge 4:

curl -i -X POST "$API_URL/orders" -H "Authorization: Bearer $(cat access-token.txt)" -H 'Content-Type: application/json' --data '{"id":"signed-order","quantity":4}'

Erwarte HTTP 201 und die Summe 1100 (4 × 250 + 100). Rufe dieselbe gespeicherte Bestellung mit dem Zugriffstoken ab:

curl -i "$API_URL/orders?id=signed-order" -H "Authorization: Bearer $(cat access-token.txt)"

Erwarte HTTP 200 mit derselben ID, Menge und berechneten Summe. Prüfe das native Element unabhängig:

aws dynamodb get-item \
  --table-name labex-a05-orders \
  --key '{"id":{"S":"signed-order"}}' \
  --consistent-read \
  --query Item

Wiederhole jetzt den Lesevorgang ohne Token:

curl -i "$API_URL/orders?id=signed-order"

Erwarte HTTP 401 Unauthorized, ohne privaten Datensatz in der Antwort. Teste auch einen anonymen Schreibvorgang:

curl -i -X POST "$API_URL/orders" -H 'Content-Type: application/json' --data '{"id":"reject-missing","quantity":9}'

Er muss ebenfalls 401 zurückgeben und darf kein Element erstellen. Private Lese- und Schreibvorgänge benötigen beide Autorisierung.

Das bereitgestellte Fixture mit falscher Signatur hat ein verändertes Signaturbyte. Ihm kann nicht vertraut werden, auch wenn seine sichtbaren Claim-Daten plausibel aussehen:

curl -i -X POST "$API_URL/orders" -H "Authorization: Bearer $(cat bad-signature-token.txt)" -H 'Content-Type: application/json' --data '{"id":"reject-signature","quantity":9}'

Erwarte 401. Ein tatsächlich ausgegebenes Token für einen anderen App-Client ist für diese API ebenfalls ungeeignet. Lies diese sichere Client-ID ein, melde dich darüber an und extrahiere sein Zugriffstoken privat:

WRONG_CLIENT_ID=$(jq -r '.wrong_client' scenario.json)
aws cognito-idp initiate-auth \
  --client-id "$WRONG_CLIENT_ID" \
  --auth-flow USER_PASSWORD_AUTH \
  --auth-parameters file://sign-in.json \
  --query AuthenticationResult \
  --output json > wrong-client.json
jq -r '.AccessToken' wrong-client.json > wrong-client-token.txt
curl -i -X POST "$API_URL/orders" -H "Authorization: Bearer $(cat wrong-client-token.txt)" -H 'Content-Type: application/json' --data '{"id":"reject-client","quantity":9}'

Erwarte 401: Eine gültige Signatur allein stimmt nicht mit dem konfigurierten Empfänger überein. Verwende als Nächstes den vorbereiteten anderen Cognito-Aussteller, der einen eigenen synthetischen Benutzer und öffentlichen Client hat:

OTHER_POOL_ID=$(jq -r '.other.pool' scenario.json)
OTHER_CLIENT_ID=$(jq -r '.other.client' scenario.json)
aws cognito-idp initiate-auth \
  --client-id "$OTHER_CLIENT_ID" \
  --auth-flow USER_PASSWORD_AUTH \
  --auth-parameters file://sign-in.json \
  --query AuthenticationResult \
  --output json > wrong-issuer.json
jq -r '.AccessToken' wrong-issuer.json > wrong-issuer-token.txt
curl -i -X POST "$API_URL/orders" -H "Authorization: Bearer $(cat wrong-issuer-token.txt)" -H 'Content-Type: application/json' --data '{"id":"reject-issuer","quantity":9}'

Erwarte 401, weil der signierte Aussteller vom erwarteten Pool abweicht. Das bereitgestellte abgelaufene Fixture ist mit einem bereits vergangenen exp signiert; es testet die Zeitregel, ohne auf den Ablauf einer aktuellen Sitzung zu warten:

curl -i -X POST "$API_URL/orders" -H "Authorization: Bearer $(cat expired-token.txt)" -H 'Content-Type: application/json' --data '{"id":"reject-expired","quantity":9}'

Erwarte 401. Ein ID-Token ist für diesen Client signiert, hat aber nicht den von der Route verlangten Zugriffstoken-Scope:

curl -i -X POST "$API_URL/orders" -H "Authorization: Bearer $(cat id-token.txt)" -H 'Content-Type: application/json' --data '{"id":"reject-id-token","quantity":9}'

Erwarte 403 wegen unzureichenden Scopes. Signatur, Aussteller, Empfänger und Tokenlebensdauer sind notwendig, liefern aber keine fehlenden Berechtigungen.

Prüfe, dass die Zustandsroute öffentlich bleibt und nur der gültige fachliche Schreibvorgang gespeichert wurde:

curl -i "$API_URL/health"
aws dynamodb scan --table-name labex-a05-orders --query Items

Erwarte für die Zustandsroute 200 und genau signed-order mit der Summe 1100. Vergleiche in AWS View tatsächliche API requests mit CloudWatch Logs: Abgelehnte Autorisierung hat ein Gateway-Ergebnis 401/403 und keine passende Funktionsausführung. Erfolgreiche private Anfragen haben tatsächliche Lambda-Ereignisse und Antworten, wobei Zugangsdatenheader ausgeschlossen sind. Diese Laufzeit- und API-Ergebnisse sowie nativen Daten sind die Bewertungsnachweise, keine Screenshots oder lokalen Erfolgsmarker.

Beispiel tatsächlich angenommener und abgelehnter API-Anfragen

Beispiel: Gültige Bestellschreib- und -lesevorgänge gaben 201/200 zurück; ungeeignete Token gaben 401/403 zurück. Die Funktionsausführungsliste und native Tabelle bestätigen unabhängig, dass abgelehnte Anfragen keinen fachlichen Schreibvorgang erzeugten.

Geschützte Routen und eigene Ressourcen entfernen

In diesem Schritt entfernst du die eigene API, Identitätsfixtures und Daten und erhältst nur unbeteiligte Referenzen.

Dein Bestand besteht aus dieser API und ihrem Authorizer, Funktion und Log-Gruppe, der Gateway-Anfrage-Log-Gruppe, Bestelltabelle, Rollenrichtlinie OrdersData, Hauptpool (zwei Clients und ein Benutzer), Pool des anderen Ausstellers (ein Client und ein Benutzer) sowie privaten Eingabe- und Antwortdateien. Referenzpool, Referenztabelle, Referenz-Logs und Rollenrichtlinie FunctionLogs sind unbeteiligt und müssen erhalten bleiben.

Lösche die API einschließlich Authorizer, Routen, Integration und Stage:

aws apigatewayv2 delete-api --api-id "$API_ID"
aws lambda delete-function --function-name labex-a05-orders-api
aws logs delete-log-group --log-group-name /aws/lambda/labex-a05-orders-api
aws logs delete-log-group --log-group-name /aws/apigateway/labex-a05
aws dynamodb delete-table \
  --table-name labex-a05-orders \
  --query TableDescription.TableName \
  --output text
aws iam delete-role-policy --role-name labex-a05-execution --policy-name OrdersData

Entferne die synthetischen Benutzer und jeden eigenen App-Client, anschließend ihre Pools:

aws cognito-idp admin-delete-user --user-pool-id "$POOL_ID" --username labex-demo
aws cognito-idp delete-user-pool-client --user-pool-id "$POOL_ID" --client-id "$CLIENT_ID"
aws cognito-idp delete-user-pool-client \
  --user-pool-id "$POOL_ID" \
  --client-id "$WRONG_CLIENT_ID"
aws cognito-idp delete-user-pool --user-pool-id "$POOL_ID"
aws cognito-idp admin-delete-user --user-pool-id "$OTHER_POOL_ID" --username labex-demo
aws cognito-idp delete-user-pool-client \
  --user-pool-id "$OTHER_POOL_ID" \
  --client-id "$OTHER_CLIENT_ID"
aws cognito-idp delete-user-pool --user-pool-id "$OTHER_POOL_ID"

Frage Bestände erfolgreich ab, um die Abwesenheit nachzuweisen; Netzwerk- und Authentifizierungsfehler sind keine Bereinigungsnachweise:

aws apigatewayv2 get-apis --query 'Items[].Name'
aws lambda list-functions --query 'Functions[].FunctionName'
aws cognito-idp list-user-pools --max-results 60 --query 'UserPools[].Name'
aws dynamodb list-tables --query TableNames

Erwarte leere API- und Funktionslisten, ausschließlich labex-a05-reference in Pool- und Tabellenlisten sowie erhaltene Referenzdaten und -Logs in AWS View. Entferne nur die aufgeführten privaten Dateien:

rm -f tokens.json access-token.txt id-token.txt wrong-client.json wrong-client-token.txt wrong-issuer.json wrong-issuer-token.txt expired-token.txt future-iat-token.txt future-nbf-token.txt bad-signature-token.txt sign-in.json

Das Löschen lokaler Dateien allein ist kein allgemeiner serverseitiger JWT-Widerruf. Auch die eigenen synthetischen Benutzer, Clients, Pools und die API wurden gelöscht. Referenzfixtures, abschließender Zugangsdatenwiderruf und VM-Abschaltung bleiben für die Bereinigung durch den Autor nach diesen schreibgeschützten Ressourcenprüfungen.

Zusammenfassung

Du hast einen Cognito-JWT-Authorizer konfiguriert und ihn für private Lese- und Schreibvorgänge verlangt, während die Zustandsroute öffentlich blieb. Du hast echte angenommene Anfragen sowie Ablehnungen bei fehlenden, veränderten, falschen Client- und Aussteller-Token, abgelaufenen Token und ID-Token getestet. Du hast tatsächliche Gateway-Ergebnisse nativen Lambda-Ausführungen und gespeicherten Daten zugeordnet und anschließend eigene API-, Identitäts- und Datenressourcen sowie private Dateien entfernt, während Referenzen erhalten geblieben sind. JWT-Autorisierung hat keinen Besitz einzelner Bestellungen implementiert.