diff --git a/docs/de/docs/tutorial/security/simple-oauth2.md b/docs/de/docs/tutorial/security/simple-oauth2.md new file mode 100644 index 000000000..ed280d486 --- /dev/null +++ b/docs/de/docs/tutorial/security/simple-oauth2.md @@ -0,0 +1,435 @@ +# Einfaches OAuth2 mit Password und Bearer + +Lassen Sie uns nun auf dem vorherigen Kapitel aufbauen und die fehlenden Teile hinzufügen, um einen vollständigen Sicherheits-Flow zu erhalten. + +## `username` und `password` entgegennehmen + +Wir werden **FastAPIs** Sicherheits-Werkzeuge verwenden, um den `username` und das `password` entgegenzunehmen. + +OAuth2 spezifiziert, dass der Client/Benutzer bei Verwendung des „Password Flow“ (den wir verwenden) die Felder `username` und `password` als Formulardaten senden muss. + +Und die Spezifikation sagt, dass die Felder so benannt werden müssen. `user-name` oder `email` würde also nicht funktionieren. + +Aber keine Sorge, Sie können sie Ihren Endbenutzern im Frontend so anzeigen, wie Sie möchten. + +Und Ihre Datenbankmodelle können beliebige andere Namen verwenden. + +Aber für die Login-*Pfadoperation* müssen wir diese Namen verwenden, um mit der Spezifikation kompatibel zu sein (und beispielsweise das integrierte API-Dokumentationssystem verwenden zu können). + +Die Spezifikation besagt auch, dass `username` und `password` als Formulardaten gesendet werden müssen (hier also kein JSON). + +### `scope` + +Ferner sagt die Spezifikation, dass der Client ein weiteres Formularfeld "`scope`" („Geltungsbereich“) senden kann. + +Der Name des Formularfelds lautet `scope` (im Singular), tatsächlich handelt es sich jedoch um einen langen String mit durch Leerzeichen getrennten „Scopes“. + +Jeder „Scope“ ist nur ein String (ohne Leerzeichen). + +Diese werden normalerweise verwendet, um bestimmte Sicherheitsberechtigungen zu deklarieren, zum Beispiel: + +* `users:read` oder `users:write` sind gängige Beispiele. +* `instagram_basic` wird von Facebook / Instagram verwendet. +* `https://www.googleapis.com/auth/drive` wird von Google verwendet. + +!!! info + In OAuth2 ist ein „Scope“ nur ein String, der eine bestimmte erforderliche Berechtigung deklariert. + + Es spielt keine Rolle, ob er andere Zeichen wie `:` enthält oder ob es eine URL ist. + + Diese Details sind implementierungsspezifisch. + + Für OAuth2 sind es einfach nur Strings. + +## Code, um `username` und `password` entgegenzunehmen. + +Lassen Sie uns nun die von **FastAPI** bereitgestellten Werkzeuge verwenden, um das zu erledigen. + +### `OAuth2PasswordRequestForm` + +Importieren Sie zunächst `OAuth2PasswordRequestForm` und verwenden Sie es als Abhängigkeit mit `Depends` in der *Pfadoperation* für `/token`: + +=== "Python 3.10+" + + ```Python hl_lines="4 78" + {!> ../../../docs_src/security/tutorial003_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="4 78" + {!> ../../../docs_src/security/tutorial003_an_py39.py!} + ``` + +=== "Python 3.8+" + + ```Python hl_lines="4 79" + {!> ../../../docs_src/security/tutorial003_an.py!} + ``` + +=== "Python 3.10+ nicht annotiert" + + !!! tip "Tipp" + Bevorzugen Sie die `Annotated`-Version, falls möglich. + + ```Python hl_lines="2 74" + {!> ../../../docs_src/security/tutorial003_py310.py!} + ``` + +=== "Python 3.8+ nicht annotiert" + + !!! tip "Tipp" + Bevorzugen Sie die `Annotated`-Version, falls möglich. + + ```Python hl_lines="4 76" + {!> ../../../docs_src/security/tutorial003.py!} + ``` + +`OAuth2PasswordRequestForm` ist eine Klassenabhängigkeit, die einen Formularbody deklariert mit: + +* Dem `username`. +* Dem `password`. +* Einem optionalen `scope`-Feld als langem String, bestehend aus durch Leerzeichen getrennten Strings. +* Einem optionalen `grant_type` („Art der Anmeldung“). + +!!! tip "Tipp" + Die OAuth2-Spezifikation *erfordert* tatsächlich ein Feld `grant_type` mit dem festen Wert `password`, aber `OAuth2PasswordRequestForm` erzwingt dies nicht. + + Wenn Sie es erzwingen müssen, verwenden Sie `OAuth2PasswordRequestFormStrict` anstelle von `OAuth2PasswordRequestForm`. + +* Eine optionale `client_id` (benötigen wir für unser Beispiel nicht). +* Ein optionales `client_secret` (benötigen wir für unser Beispiel nicht). + +!!! info + `OAuth2PasswordRequestForm` ist keine spezielle Klasse für **FastAPI**, so wie `OAuth2PasswordBearer`. + + `OAuth2PasswordBearer` lässt **FastAPI** wissen, dass es sich um ein Sicherheitsschema handelt. Daher wird es auf diese Weise zu OpenAPI hinzugefügt. + + Aber `OAuth2PasswordRequestForm` ist nur eine Klassenabhängigkeit, die Sie selbst hätten schreiben können, oder Sie hätten `Form`ular-Parameter direkt deklarieren können. + + Da es sich jedoch um einen häufigen Anwendungsfall handelt, wird er zur Vereinfachung direkt von **FastAPI** bereitgestellt. + +### Die Formulardaten verwenden + +!!! tip "Tipp" + Die Instanz der Klassenabhängigkeit `OAuth2PasswordRequestForm` verfügt, statt eines Attributs `scope` mit dem durch Leerzeichen getrennten langen String, über das Attribut `scopes` mit einer tatsächlichen Liste von Strings, einem für jeden gesendeten Scope. + + In diesem Beispiel verwenden wir keine `scopes`, aber die Funktionalität ist vorhanden, wenn Sie sie benötigen. + +Rufen Sie nun die Benutzerdaten aus der (gefakten) Datenbank ab, für diesen `username` aus dem Formularfeld. + +Wenn es keinen solchen Benutzer gibt, geben wir die Fehlermeldung „Incorrect username or password“ zurück. + +Für den Fehler verwenden wir die Exception `HTTPException`: + +=== "Python 3.10+" + + ```Python hl_lines="3 79-81" + {!> ../../../docs_src/security/tutorial003_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="3 79-81" + {!> ../../../docs_src/security/tutorial003_an_py39.py!} + ``` + +=== "Python 3.8+" + + ```Python hl_lines="3 80-82" + {!> ../../../docs_src/security/tutorial003_an.py!} + ``` + +=== "Python 3.10+ nicht annotiert" + + !!! tip "Tipp" + Bevorzugen Sie die `Annotated`-Version, falls möglich. + + ```Python hl_lines="1 75-77" + {!> ../../../docs_src/security/tutorial003_py310.py!} + ``` + +=== "Python 3.8+ nicht annotiert" + + !!! tip "Tipp" + Bevorzugen Sie die `Annotated`-Version, falls möglich. + + ```Python hl_lines="3 77-79" + {!> ../../../docs_src/security/tutorial003.py!} + ``` + +### Das Passwort überprüfen + +Zu diesem Zeitpunkt liegen uns die Benutzerdaten aus unserer Datenbank vor, das Passwort haben wir jedoch noch nicht überprüft. + +Lassen Sie uns diese Daten zunächst in das Pydantic-Modell `UserInDB` einfügen. + +Sie sollten niemals Klartext-Passwörter speichern, daher verwenden wir ein (gefaktes) Passwort-Hashing-System. + +Wenn die Passwörter nicht übereinstimmen, geben wir denselben Fehler zurück. + +#### Passwort-Hashing + +„Hashing“ bedeutet: Konvertieren eines Inhalts (in diesem Fall eines Passworts) in eine Folge von Bytes (ein schlichter String), die wie Kauderwelsch aussieht. + +Immer wenn Sie genau den gleichen Inhalt (genau das gleiche Passwort) übergeben, erhalten Sie genau den gleichen Kauderwelsch. + +Sie können jedoch nicht vom Kauderwelsch zurück zum Passwort konvertieren. + +##### Warum Passwort-Hashing verwenden? + +Wenn Ihre Datenbank gestohlen wird, hat der Dieb nicht die Klartext-Passwörter Ihrer Benutzer, sondern nur die Hashes. + +Der Dieb kann also nicht versuchen, die gleichen Passwörter in einem anderen System zu verwenden (da viele Benutzer überall das gleiche Passwort verwenden, wäre dies gefährlich). + +=== "Python 3.10+" + + ```Python hl_lines="82-85" + {!> ../../../docs_src/security/tutorial003_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="82-85" + {!> ../../../docs_src/security/tutorial003_an_py39.py!} + ``` + +=== "Python 3.8+" + + ```Python hl_lines="83-86" + {!> ../../../docs_src/security/tutorial003_an.py!} + ``` + +=== "Python 3.10+ nicht annotiert" + + !!! tip "Tipp" + Bevorzugen Sie die `Annotated`-Version, falls möglich. + + ```Python hl_lines="78-81" + {!> ../../../docs_src/security/tutorial003_py310.py!} + ``` + +=== "Python 3.8+ nicht annotiert" + + !!! tip "Tipp" + Bevorzugen Sie die `Annotated`-Version, falls möglich. + + ```Python hl_lines="80-83" + {!> ../../../docs_src/security/tutorial003.py!} + ``` + +#### Über `**user_dict` + +`UserInDB(**user_dict)` bedeutet: + +*Übergib die Schlüssel und Werte des `user_dict` direkt als Schlüssel-Wert-Argumente, äquivalent zu:* + +```Python +UserInDB( + username = user_dict["username"], + email = user_dict["email"], + full_name = user_dict["full_name"], + disabled = user_dict["disabled"], + hashed_password = user_dict["hashed_password"], +) +``` + +!!! info + Eine ausführlichere Erklärung von `**user_dict` finden Sie in [der Dokumentation für **Extra Modelle**](../extra-models.md#uber-user_indict){.internal-link target=_blank}. + +## Den Token zurückgeben + +Die Response des `token`-Endpunkts muss ein JSON-Objekt sein. + +Es sollte einen `token_type` haben. Da wir in unserem Fall „Bearer“-Token verwenden, sollte der Token-Typ "`bearer`" sein. + +Und es sollte einen `access_token` haben, mit einem String, der unseren Zugriffstoken enthält. + +In diesem einfachen Beispiel gehen wir einfach völlig unsicher vor und geben denselben `username` wie der Token zurück. + +!!! tip "Tipp" + Im nächsten Kapitel sehen Sie eine wirklich sichere Implementierung mit Passwort-Hashing und JWT-Tokens. + + Aber konzentrieren wir uns zunächst auf die spezifischen Details, die wir benötigen. + +=== "Python 3.10+" + + ```Python hl_lines="87" + {!> ../../../docs_src/security/tutorial003_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="87" + {!> ../../../docs_src/security/tutorial003_an_py39.py!} + ``` + +=== "Python 3.8+" + + ```Python hl_lines="88" + {!> ../../../docs_src/security/tutorial003_an.py!} + ``` + +=== "Python 3.10+ nicht annotiert" + + !!! tip "Tipp" + Bevorzugen Sie die `Annotated`-Version, falls möglich. + + ```Python hl_lines="83" + {!> ../../../docs_src/security/tutorial003_py310.py!} + ``` + +=== "Python 3.8+ nicht annotiert" + + !!! tip "Tipp" + Bevorzugen Sie die `Annotated`-Version, falls möglich. + + ```Python hl_lines="85" + {!> ../../../docs_src/security/tutorial003.py!} + ``` + +!!! tip "Tipp" + Gemäß der Spezifikation sollten Sie ein JSON mit einem `access_token` und einem `token_type` zurückgeben, genau wie in diesem Beispiel. + + Das müssen Sie selbst in Ihrem Code tun und sicherstellen, dass Sie diese JSON-Schlüssel verwenden. + + Es ist fast das Einzige, woran Sie denken müssen, es selbst richtigzumachen und die Spezifikationen einzuhalten. + + Den Rest erledigt **FastAPI** für Sie. + +## Die Abhängigkeiten aktualisieren + +Jetzt werden wir unsere Abhängigkeiten aktualisieren. + +Wir möchten den `current_user` *nur* erhalten, wenn dieser Benutzer aktiv ist. + +Daher erstellen wir eine zusätzliche Abhängigkeit `get_current_active_user`, die wiederum `get_current_user` als Abhängigkeit verwendet. + +Beide Abhängigkeiten geben nur dann einen HTTP-Error zurück, wenn der Benutzer nicht existiert oder inaktiv ist. + +In unserem Endpunkt erhalten wir also nur dann einen Benutzer, wenn der Benutzer existiert, korrekt authentifiziert wurde und aktiv ist: + +=== "Python 3.10+" + + ```Python hl_lines="58-66 69-74 94" + {!> ../../../docs_src/security/tutorial003_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="58-66 69-74 94" + {!> ../../../docs_src/security/tutorial003_an_py39.py!} + ``` + +=== "Python 3.8+" + + ```Python hl_lines="59-67 70-75 95" + {!> ../../../docs_src/security/tutorial003_an.py!} + ``` + +=== "Python 3.10+ nicht annotiert" + + !!! tip "Tipp" + Bevorzugen Sie die `Annotated`-Version, falls möglich. + + ```Python hl_lines="56-64 67-70 88" + {!> ../../../docs_src/security/tutorial003_py310.py!} + ``` + +=== "Python 3.8+ nicht annotiert" + + !!! tip "Tipp" + Bevorzugen Sie die `Annotated`-Version, falls möglich. + + ```Python hl_lines="58-66 69-72 90" + {!> ../../../docs_src/security/tutorial003.py!} + ``` + +!!! info + Der zusätzliche Header `WWW-Authenticate` mit dem Wert `Bearer`, den wir hier zurückgeben, ist ebenfalls Teil der Spezifikation. + + Jeder HTTP-(Fehler-)Statuscode 401 „UNAUTHORIZED“ soll auch einen `WWW-Authenticate`-Header zurückgeben. + + Im Fall von Bearer-Tokens (in unserem Fall) sollte der Wert dieses Headers `Bearer` lauten. + + Sie können diesen zusätzlichen Header tatsächlich weglassen und es würde trotzdem funktionieren. + + Aber er wird hier bereitgestellt, um den Spezifikationen zu entsprechen. + + Außerdem gibt es möglicherweise Tools, die ihn erwarten und verwenden (jetzt oder in der Zukunft) und das könnte für Sie oder Ihre Benutzer jetzt oder in der Zukunft nützlich sein. + + Das ist der Vorteil von Standards ... + +## Es in Aktion sehen + +Öffnen Sie die interaktive Dokumentation: http://127.0.0.1:8000/docs. + +### Authentifizieren + +Klicken Sie auf den Button „Authorize“. + +Verwenden Sie die Anmeldedaten: + +Benutzer: `johndoe` + +Passwort: `secret`. + + + +Nach der Authentifizierung im System sehen Sie Folgendes: + + + +### Die eigenen Benutzerdaten ansehen + +Verwenden Sie nun die Operation `GET` mit dem Pfad `/users/me`. + +Sie erhalten Ihre Benutzerdaten: + +```JSON +{ + "username": "johndoe", + "email": "johndoe@example.com", + "full_name": "John Doe", + "disabled": false, + "hashed_password": "fakehashedsecret" +} +``` + + + +Wenn Sie auf das Schlosssymbol klicken und sich abmelden und dann den gleichen Vorgang nochmal versuchen, erhalten Sie einen HTTP 401 Error: + +```JSON +{ + "detail": "Not authenticated" +} +``` + +### Inaktiver Benutzer + +Versuchen Sie es nun mit einem inaktiven Benutzer und authentisieren Sie sich mit: + +Benutzer: `alice`. + +Passwort: `secret2`. + +Und versuchen Sie, die Operation `GET` mit dem Pfad `/users/me` zu verwenden. + +Sie erhalten die Fehlermeldung „Inactive user“: + +```JSON +{ + "detail": "Inactive user" +} +``` + +## Zusammenfassung + +Sie verfügen jetzt über die Tools, um ein vollständiges Sicherheitssystem basierend auf `username` und `password` für Ihre API zu implementieren. + +Mit diesen Tools können Sie das Sicherheitssystem mit jeder Datenbank und jedem Benutzer oder Datenmodell kompatibel machen. + +Das einzige fehlende Detail ist, dass es noch nicht wirklich „sicher“ ist. + +Im nächsten Kapitel erfahren Sie, wie Sie eine sichere Passwort-Hashing-Bibliothek und JWT-Token verwenden.