mirror of https://github.com/tiangolo/fastapi.git
🌐 Add German translation for `docs/de/docs/tutorial/security/simple-oauth2.md` (#10504)
This commit is contained in:
parent
9899a074d3
commit
504fb2a64f
|
|
@ -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).
|
||||
|
||||
### <abbr title="Geltungsbereich">`scope`</abbr>
|
||||
|
||||
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 <abbr title="JSON Web Tokens">JWT</abbr>-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: <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
|
||||
|
||||
### Authentifizieren
|
||||
|
||||
Klicken Sie auf den Button „Authorize“.
|
||||
|
||||
Verwenden Sie die Anmeldedaten:
|
||||
|
||||
Benutzer: `johndoe`
|
||||
|
||||
Passwort: `secret`.
|
||||
|
||||
<img src="/img/tutorial/security/image04.png">
|
||||
|
||||
Nach der Authentifizierung im System sehen Sie Folgendes:
|
||||
|
||||
<img src="/img/tutorial/security/image05.png">
|
||||
|
||||
### 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"
|
||||
}
|
||||
```
|
||||
|
||||
<img src="/img/tutorial/security/image06.png">
|
||||
|
||||
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 <abbr title="JSON Web Tokens">JWT</abbr>-Token verwenden.
|
||||
Loading…
Reference in New Issue