[PYTHON] Implementieren Sie Custom Authorizer für die Firebase-Authentifizierung in Chalice

Einführung

Wenn Sie über das API-Gateway auf Lambda-Funktionen zugreifen, möchten Sie diesen Zugriff möglicherweise steuern. Beispielsweise können die folgenden Fälle betrachtet werden.

Um dieses Problem zu lösen, ist beim Aufrufen einer Lambda-Funktion über das API-Gateway ein Mechanismus vorhanden, mit dem die Überprüfung unmittelbar vor der Anforderung durchgeführt werden kann. Dies wird als Lambda Authorizer bezeichnet.

Die Abbildung in Lambda Authorizer Description Page ist unten angegeben. Durch Aufrufen der Lambda-Auth-Funktion in dieser Abbildung vor der Lambda-Funktion (auf die Sie den Zugriff beschränken möchten) und deren Genehmigung können Sie den Zugriff auf die letztere Funktion einschränken, ohne eine besonders umfangreiche Zugriffssteuerung zu schreiben.

custom-auth-workflow.png

Wenn Sie eine vorhandene Lambda-Funktion als Lambda-Autorisierer verwenden, nennt Kelch sie einen benutzerdefinierten Autorisierer. Hier,

--Erstellen Sie eine Lambda-Authentifizierungsfunktion mit Chalice, um das JWT-Token für die Firebase-Authentifizierung zu authentifizieren

Es werden zwei Methoden beschrieben.

Build-in Authorizer Lambda Auth Function for Firebase Authentication

Vorbereitungen

Kelcheinstellungen

Erstellen Sie ein Projekt für die Lambda Auth-Funktion. Ich werde die Installation usw. weglassen, aber lesen Sie sie hier so, als hätten Sie Kelch an einem Ort installiert, der global verwendet werden kann (Sie können ihn durch den Status ersetzen, in dem virtualenv aktiviert ist).

#Erstellen Sie ein neues Projekt
$ chalice new-project authorizer
$ cd authorizer

# firebase-Stellen Sie den Administrator in den Anbieter und erstellen Sie ihn nicht lokal bei der Bereitstellung
#Die Bereitstellung in meiner Umgebung dauerte 7 Minuten, daher mache ich das, um Zeit zu sparen
$ mkdir vendor
$ pip install firebase-admin -t vendor

# firebase-Platzieren Sie die JSON-Datei für den Administrator
#Feste Dateien werden nur hochgeladen, wenn sie in Chalicelib abgelegt sind
#Lambda steigt auf S3 auf. Wenn Sie sich also Sorgen machen, ist es besser, mit MKS zu verschlüsseln und zu entschlüsseln.
$ mkdir chalicelib
$ cp "<firebase-Admin-Einstellungen json>" chalicelib/firebase-adminsdk-dev.json

Hauptdateien usw.


authorizer
├── app.py
├── .chalice
│   └── config.json
├── chalicelib
│   └── firebase-adminsdk-dev.json
└── vendor
    └── (Viele installiert)

Schreiben Sie nun den Hauptcode wie folgt: Diesmal mit stage = dev bereitstellen, aber nach Bedarf neu schreiben.

json:.chalice/config.json


{
  "version": "2.0",
  "app_name": "authorizer",
  "stages": {
    "dev": {
      "api_gateway_stage": "api",
      "environment_variables": {
        "FIREBASE_CONFIGFILE": "firebase-adminsdk-dev.json"
      }
    }
  }
}

app.py


#!/usr/bin/python
# -*- coding: utf-8 -*-

import os
import logging

from chalice import Chalice, AuthResponse

import firebase_admin
import firebase_admin.auth as firebase_auth

logger = logging.getLogger()
app = Chalice(app_name='authorizer')

#Laden Sie die Einstellungen und die Firebase-Admin initialisieren
firebase_cred = firebase_admin.credentials.Certificate(
    os.path.join(
        os.path.dirname(__file__), 'chalicelib', 
        os.environ['FIREBASE_CONFIGFILE']))
firebase_admin.initialize_app(firebase_cred)

@app.authorizer()
def authorizer(auth_request):
    '''
Die Entität des benutzerdefinierten Autorisierers.
Geben Sie Lambda für diese Entität aus einem anderen Kelchprojekt an.
    '''
    #Übergeben Sie das von Firebase Auth erhaltene JWT-Token im Authorization-Header
    # curl -s '<API URL>' -H 'Authorization: <JWT Token>' | jq . 
    try:
        jwt_token = auth_request.token
        crimes = firebase_auth.verify_id_token(jwt_token)
        context = dict(uid=crimes['uid'])
        return AuthResponse(routes=['*'], principal_id=crimes['uid'], context=context)
    except Exception as e:
        logger.exception(e)
        return AuthResponse(routes=[], principal_id='deny')


@app.route('/', authorizer=authorizer)
def index():
    '''
Zur Überprüfung und zur Bereitstellung des Authorizers.
Wenn keine oder mehrere Routen vorhanden sind, wird der benutzerdefinierte Autorisierer ebenfalls nicht bereitgestellt.
    '''
    return { 'AuthContext': app.current_request.context }

Erstellen Sie eine Funktion, die das JWT-Token mit dem Dekorator "@ app.authorizer ()" verarbeitet und je nach Ergebnis eine "AuthResponse" zurückgibt. Erstellen Sie in AuthResponse "Route, auf die über das entsprechende Token zugegriffen werden kann (Pfad auf API-Gateway-Ebene)", "Principal_ID", die den Benutzer eindeutig identifiziert, und "Kontext", den Sie zum Zeitpunkt der Authentifizierung zusätzlich erfassen möchten, und übergeben Sie ihn an die nachfolgende Funktion. Und schließen Sie diese ein.

Einzelheiten dazu, welche Art von AuthResponse erstellt und zurückgegeben werden soll, finden Sie in den folgenden Dokumenten.

Beachten Sie auch, dass ** @ app.authorizer () Klammern ** erfordert. Ohne Klammern wird es etwas anderes und funktioniert nicht gut.

Deploy

Diesmal mit "Kelch bereitstellen" bereitstellen. Bei Bedarf können Sie ein "Kelchpaket" erstellen und es dann zur Bereitstellung in CloudFormation werfen.

#Testen Sie, ob es lokal funktioniert
# @app.authorizer()Nur im Fall von funktioniert es auch lokal
#Beachten Sie, dass andere Autorisierer nicht lokal arbeiten
$ chalice local 

#Bereitstellung in AWS
$ chalice deploy --profile chalice
Creating deployment package.
Creating IAM role: authorizer-dev-api_handler
Creating lambda function: authorizer-dev
Creating IAM role: authorizer-dev-authorizer
Creating lambda function: authorizer-dev-authorizer
Creating Rest API
Resources deployed:
  - Lambda ARN: arn:aws:lambda:ap-northeast-1:************:function:authorizer-dev
  - Lambda ARN: arn:aws:lambda:ap-northeast-1:************:function:authorizer-dev-authorizer
  - Rest API URL: https://**********.execute-api.ap-northeast-1.amazonaws.com/api/

Das ist also die ganze Bereitstellungsarbeit.

Testing

Überprüfen Sie, ob Sie tatsächlich darauf zugreifen können. Es ist nicht reproduzierbar, aber manchmal ist es nach der Bereitstellung für eine Weile nicht verfügbar. Wenn dies passiert (= `` wird zurückgegeben), warten Sie bitte einen Moment und versuchen Sie es erneut.

#Kein Autorisierungsheader
$ curl -s https://**********.execute-api.ap-northeast-1.amazonaws.com/api/ | jq .
{
  "message": "Unauthorized"
}

#Wenn die Authentifizierung fehlschlägt=[]Und der Endpunkt/Habe keinen Zugriff
$ curl -s https://**********.execute-api.ap-northeast-1.amazonaws.com/api/ -H 'Authorization: hoge'| jq .
{
  "Message": "User is not authorized to access this resource"
}

#Wenn Sie das JWT-Token übergeben, können Sie normal darauf zugreifen
# AuthContext.Der im Kontext übergebene Wert wird an den Autorisierer ausgegeben
$ curl -s https://**********.execute-api.ap-northeast-1.amazonaws.com/api/ -H 'Authorization: <Firebase Auth JWT-Token>' | jq .
{
  "AuthContext": {
    "resourceId": "........",
    "authorizer": {
      "uid": "**********",
      "principalId": "**********",
      "integrationLatency": 190
    },
    "resourcePath": "/",
    "httpMethod": "GET",
    "extendedRequestId": "*************",
    "requestTime": "07/May/2020:00:33:11 +0000",
    "path": "/api/",
    ... (Unterlassung) ...
  }
}

Wie Sie sehen, konnten wir Authorizer auf sehr einfache Weise implementieren.

Firebase Cost and Authorization Caching

Wenn nichts festgelegt ist, wird die Authorizer-Funktion bei jedem Zugriff auf Lambda aufgerufen.

** Alle Authentifizierungen außer der Telefonauthentifizierung mit Firebase-Autorisierung sind laut Preisliste kostenlos **, aber es ist ein wenig, Lambda anzurufen Es wird eine Gebühr erhoben.

Daher kann der Cache des Authentifizierungsergebnisses für einen bestimmten Zeitraum verwendet werden. Dies ist auch in der ersten Abbildung unter "Richtlinie wird zwischengespeichert" zu sehen.

custom-auth-workflow.png

Um das Caching zu aktivieren, geben Sie "ttl_seconds" wie folgt an: Beachten Sie jedoch, dass die Einstellungen hier Einstellungen in API Gateway sind, sodass sie nicht mit dem später beschriebenen benutzerdefinierten Autorisierer zusammenhängen.

Auszug aus der Authorizer-Implementierung


@app.authorizer(ttl_seconds=120)
def authorizer(auth_request):
    ....

Build-in Authorizer vs Customer Authorizer

Das Schreiben Ihrer eigenen Logik in einem Projekt und das Durchführen der Authentifizierung wird als integrierter Autorisierer in Chalice bezeichnet. Auf der anderen Seite können Sie stattdessen den von Chalice erstellten Authorizer verwenden. Dazu gehören IAMAuthorizer, CognitoUserPoolAuthorizer und CustomAuthorizer, die vorhandene AWS-Ressourcen zur Authentifizierung verwenden. Von diesen ist "CustomAuthorizer" ein Authorizer für die Verwendung vorhandener Lambda-Funktionen als Authorizer.

Wenn Sie es als einfache Aufgabe ausführen möchten, können Sie Build-in Authorier verwenden, aber die Firebase-Admin-Bibliothek allein hat eine Kapazität von fast 10 MB (ca. 20 MB, wenn ein binärer Build ausgeführt wird). Ich möchte nicht die Kapazität von Lambda für eine Bibliothek nutzen, die nur an einer Stelle für die Authentifizierung verwendet wird. Lassen Sie uns diesen Teil als eine weitere Lambda-Funktion als CustomAuthorizer ausschneiden.

Another Chalice Project with CustomAuthorizer

``


sample
└── app.py

app.py


#!/usr/bin/python
# -*- coding: utf-8 -*-

import os

from chalice import Chalice, CustomAuthorizer

app = Chalice(app_name='sample')

# authorizer_.chalice/config.
region = 'ap-northeast-1'
lambda_arn = '<Notieren Sie sich den Arn der Authorizer-Funktion, die im Authorizer-Projekt erstellt wurde, das vor dem Einrichten von Chalice erstellt wurde. In diesem Fall sieht es aus wie "arn: aws: lambda: <region>: <aws-account-no>: function: authorizer-dev-authorizer". Der uri-Teil wie die Hauptdatei kann in json ARN der zuvor erstellten Authorizer-Funktion ausgeschnitten werden>'
authorizer_uri = f'arn:aws:apigateway:{region}:lambda:path/2015-03-31/functions/{lambda_arn}/invocations'

# ttl_Wenn keine Sekunden vorhanden sind, wird standardmäßig ein Cache von 300 Sekunden verwendet.
authorizer = CustomAuthorizer(
    'FirebaseAuthorizer', 
    ttl_seconds=60,
    authorizer_uri=authorizer_uri)


@app.route('/private', authorizer=authorizer)
def private_function():
    return {'RequestContext': app.current_request.context}


@app.route('/public')
def public_function():
    return {'message': 'success'}

Erstellen Sie eine CustomAuthorizer-Instanz wie oben beschrieben. Sie können einen beliebigen Namen für das erste Argument eingeben. authorizer_uri gibt die Lambda-Funktion an, die im obigen Format als Authorizer aufgerufen werden soll.

The URI of the lambda function to use for the custom authorizer. This usually has the form arn:aws:apigateway:{region}:lambda:path/2015-03-31/functions/{lambda_arn}/invocations. Quote-https: //chalice.readthedocs.io/en/latest/api.html#CustomAuthorizer.authorizer_uri

Führen Sie es lokal mit dem Befehl chalice local aus, um es zu testen. Beachten Sie jedoch, dass es nur für den integrierten Authorizer lokal funktioniert. Wenn Sie einen bestimmten Benutzer in der lokalen Umgebung betreiben möchten, können Sie den Ausführungsstatus mit Umgebungsvariablen der Stufe überprüfen und den Autorisierer für lokal einfügen.

#Kann normal ohne Authorizer ausgeführt werden
$ curl -s http://localhost:8000/public/ | jq . 
{
  "message": "success"
}

#CustomAuthorizer kann nicht lokal implementiert werden
$ curl -s http://localhost:8000/private/ | jq . 
{
  "RequestContext": {
    "httpMethod": "GET",
    "resourcePath": "/private",
    "identity": {
      "sourceIp": "127.0.0.1"
    },
    "path": "/private/"
  }
}

#Auf dem lokalen Kelch wird eine Meldung ähnlich der folgenden angezeigt
# UserWarning: CustomAuthorizer is not a supported in local mode. All requests made against a route will be authorized to allow local testing.

Testing

Nach der Bereitstellung mit chalice deploy --profile chalice überprüfen wir die Reihenfolge wie zuvor, aber das Verhalten wird ab dem Moment, in dem wir den Header Authorization einfügen, merkwürdig. Hier sollte "Nachricht" angezeigt werden: "Benutzer ist nicht berechtigt, auf diese Ressource zuzugreifen".

#Sie können auf die Öffentlichkeit zugreifen
$ curl -s https://********.execute-api.ap-northeast-1.amazonaws.com/api/public/ | jq .
{
  "message": "success"
}

#Auf Private kann ohne Authentifizierung nicht zugegriffen werden
$ curl -s https://**********.execute-api.ap-northeast-1.amazonaws.com/api/private/ | jq .
{
  "message": "Unauthorized"
}

# !?!?
$ curl -s https://**********.execute-api.ap-northeast-1.amazonaws.com/api/private/ -H 'Authorization: hoge'| jq .
{
  "message": null
}

Führen Sie Custom Authorizer aus

In diesem Zusammenhang weist die Kelchformel auf ein ähnliches Problem hin.

Authorizer won't work on after deployment. - https://github.com/aws/chalice/issues/670

Die Lösung lautet: "Öffnen Sie das API-Gateway in der Verwaltungskonsole und überschreiben Sie den Autorisierer, um es zu verwenden." In der Tat können Sie es so verwenden.

Der Grund dafür ist, dass die Bereitstellung mit CustomAuthorizer allein keine ressourcenbasierte Richtlinie zum Aufrufen eines vorhandenen Authorizer-Lambda vom Standard-API-Gateway ** geben kann (Lambda, das von CustomAuthorizer angegeben wird, ist das aktuelle Chalice-Projekt). Es ist eine völlig unabhängige Ressource, daher ist es sicherlich nicht gut, Änderungen an dieser Ressource vorzunehmen. Wenn Sie den Autorisierer mithilfe des oben beschriebenen Verfahrens überschreiben, können Sie die ressourcenbasierte Richtlinie von Lambda automatisch zuweisen. Beachten Sie jedoch, dass Sie beim Ändern des Namens des Autorisierers "Löschen ⇒ Generieren" ausführen und die ID des Autorisierers in eine andere geändert wird, sodass Sie die dem vorhandenen Lambda erteilte Berechtigung nicht verwenden können. Wenn Sie den Namen nicht ändern, ändert sich die ID nicht.

Wenn Sie die Verwaltungskonsole nicht verwenden Wenn Sie zulassen, dass vorhandene Funktionen vom API-Gateway mit "aws lambda add-allow" usw. aufgerufen werden, können Sie sie normal aufrufen.

add-Ein Beispiel für eine Erlaubnis


$ aws lambda add-permission \
    --function-name authorizer-dev-authorizer \
    --action lambda:InvokeFunction \
    --statement-id <Geben Sie eine entsprechende UID ein> \
    --principal apigateway.amazonaws.com

In diesem Beispiel sind Aufrufe von jedem API-Gateway zulässig. Wenn Sie also strenger sein möchten, geben Sie bitte Bedingung an.

Re-Testing

#Sie können auf die Öffentlichkeit zugreifen
$ curl -s https://********.execute-api.ap-northeast-1.amazonaws.com/api/public/ | jq .
{
  "message": "success"
}

#Auf Private kann ohne Authentifizierung nicht zugegriffen werden
$ curl -s https://**********.execute-api.ap-northeast-1.amazonaws.com/api/private/ | jq .
{
  "message": "Unauthorized"
}

#Wenn die Authentifizierung fehlschlägt=[]Und der Endpunkt/Habe keinen Zugriff
$ curl -s https://**********.execute-api.ap-northeast-1.amazonaws.com/api/private/ -H 'Authorization: hoge'| jq .
{
  "Message": "User is not authorized to access this resource"
}

#Wenn Sie das JWT-Token übergeben, können Sie normal darauf zugreifen
# AuthContext.Der im Kontext übergebene Wert wird an den Autorisierer ausgegeben
$ curl -s https://**********.execute-api.ap-northeast-1.amazonaws.com/api/private/ -H 'Authorization: <Firebase Auth JWT-Token>' | jq .
{
  "AuthContext": {
    "resourceId": "........",
    "authorizer": {
      "uid": "**********",
      "principalId": "**********",
      "integrationLatency": 153
    },
    "resourcePath": "/private",
    "httpMethod": "GET",
    "extendedRequestId": "**************",
    "requestTime": "07/May/2020:02:12:29 +0000",
    "path": "/api/private/",
    ... (Unterlassung) ...
  }
}

Zusammenfassung

Es gibt einige Fallstricke beim Festlegen von Berechtigungen bei Verwendung von CustomAuthorizer, aber ansonsten konnte ich mit der Firebase-Authentifizierung mit sehr kurzem Code arbeiten.

Da ich Firebase verwende, denke ich, dass es keine Probleme geben wird, wenn es überhaupt mit Firebase abgeschlossen wird. Wenn Sie AWS verwenden, können Sie auch Cognito verwenden. Es ist nicht überraschend, aber ich hoffe, es ist hilfreich für Leute wie mich, die dies grundsätzlich unter AWS tun möchten und die Firebase-Authentifizierung, die neben der Telefonauthentifizierung auch eine kostenlose Benutzeroberfläche bietet, als Grundlage für IDaaS verwenden möchten. ist.

Recommended Posts

Implementieren Sie Custom Authorizer für die Firebase-Authentifizierung in Chalice
Implementieren Sie den GraphConvLayer von DeepChem in der benutzerdefinierten Ebene von PyTorch
Implementieren Sie einen benutzerdefinierten View Decorator mit Pyramid
Implementieren Sie den GraphGatherLayer von DeepChem mit der benutzerdefinierten Ebene von PyTorch
Implementieren Sie ein benutzerdefiniertes Benutzermodell in Django
[Implementierung zum Lernen] Implementieren Sie Stratified Sampling in Python (1)
[Fast API + Firebase] Aufbau eines API-Servers für die Bearer-Authentifizierung