Movatterモバイル変換


[0]ホーム

URL:


Saltar a contenido
Join theFastAPI Cloud waiting list 🚀
Follow@fastapi onX (Twitter) to stay updated
FollowFastAPI onLinkedIn to stay updated
Subscribe to theFastAPI and friends newsletter 🎉
sponsor
sponsor
sponsor
sponsor
sponsor
sponsor
sponsor
sponsor
sponsor
sponsor
sponsor

Simple OAuth2 con Password y Bearer

🌐 Traducción por IA y humanos

Esta traducción fue hecha por IA guiada por humanos. 🤝

Podría tener errores al interpretar el significado original, o sonar poco natural, etc. 🤖

Puedes mejorar esta traducciónayudándonos a guiar mejor al LLM de IA.

Versión en inglés

Ahora vamos a construir a partir del capítulo anterior y agregar las partes faltantes para tener un flujo de seguridad completo.

Obtener elusername ypassword

Vamos a usar las utilidades de seguridad deFastAPI para obtener elusername ypassword.

OAuth2 especifica que cuando se utiliza el "password flow" (que estamos usando), el cliente/usuario debe enviar camposusername ypassword como form data.

Y la especificación dice que los campos deben llamarse así. Por lo queuser-name oemail no funcionarían.

Pero no te preocupes, puedes mostrarlo como quieras a tus usuarios finales en el frontend.

Y tus modelos de base de datos pueden usar cualquier otro nombre que desees.

Pero para lapath operation de inicio de sesión, necesitamos usar estos nombres para ser compatibles con la especificación (y poder, por ejemplo, utilizar el sistema de documentación integrada de la API).

La especificación también establece que elusername ypassword deben enviarse como form data (por lo que no hay JSON aquí).

scope

La especificación también indica que el cliente puede enviar otro campo del formulario llamado "scope".

El nombre del campo del formulario esscope (en singular), pero en realidad es un string largo con "scopes" separados por espacios.

Cada "scope" es simplemente un string (sin espacios).

Normalmente se utilizan para declarar permisos de seguridad específicos, por ejemplo:

  • users:read ousers:write son ejemplos comunes.
  • instagram_basic es usado por Facebook / Instagram.
  • https://www.googleapis.com/auth/drive es usado por Google.

Información

En OAuth2 un "scope" es solo un string que declara un permiso específico requerido.

No importa si tiene otros caracteres como: o si es una URL.

Esos detalles son específicos de la implementación.

Para OAuth2 son solo strings.

Código para obtener elusername ypassword

Ahora vamos a usar las utilidades proporcionadas porFastAPI para manejar esto.

OAuth2PasswordRequestForm

Primero, importaOAuth2PasswordRequestForm, y úsalo como una dependencia conDepends en lapath operation para/token:

fromtypingimportAnnotatedfromfastapiimportDepends,FastAPI,HTTPException,statusfromfastapi.securityimportOAuth2PasswordBearer,OAuth2PasswordRequestFormfrompydanticimportBaseModelfake_users_db={"johndoe":{"username":"johndoe","full_name":"John Doe","email":"johndoe@example.com","hashed_password":"fakehashedsecret","disabled":False,},"alice":{"username":"alice","full_name":"Alice Wonderson","email":"alice@example.com","hashed_password":"fakehashedsecret2","disabled":True,},}app=FastAPI()deffake_hash_password(password:str):return"fakehashed"+passwordoauth2_scheme=OAuth2PasswordBearer(tokenUrl="token")classUser(BaseModel):username:stremail:str|None=Nonefull_name:str|None=Nonedisabled:bool|None=NoneclassUserInDB(User):hashed_password:strdefget_user(db,username:str):ifusernameindb:user_dict=db[username]returnUserInDB(**user_dict)deffake_decode_token(token):# This doesn't provide any security at all# Check the next versionuser=get_user(fake_users_db,token)returnuserasyncdefget_current_user(token:Annotated[str,Depends(oauth2_scheme)]):user=fake_decode_token(token)ifnotuser:raiseHTTPException(status_code=status.HTTP_401_UNAUTHORIZED,detail="Not authenticated",headers={"WWW-Authenticate":"Bearer"},)returnuserasyncdefget_current_active_user(current_user:Annotated[User,Depends(get_current_user)],):ifcurrent_user.disabled:raiseHTTPException(status_code=400,detail="Inactive user")returncurrent_user@app.post("/token")asyncdeflogin(form_data:Annotated[OAuth2PasswordRequestForm,Depends()]):user_dict=fake_users_db.get(form_data.username)ifnotuser_dict:raiseHTTPException(status_code=400,detail="Incorrect username or password")user=UserInDB(**user_dict)hashed_password=fake_hash_password(form_data.password)ifnothashed_password==user.hashed_password:raiseHTTPException(status_code=400,detail="Incorrect username or password")return{"access_token":user.username,"token_type":"bearer"}@app.get("/users/me")asyncdefread_users_me(current_user:Annotated[User,Depends(get_current_active_user)],):returncurrent_user
🤓 Other versions and variants

Tip

Prefer to use theAnnotated version if possible.

fromfastapiimportDepends,FastAPI,HTTPException,statusfromfastapi.securityimportOAuth2PasswordBearer,OAuth2PasswordRequestFormfrompydanticimportBaseModelfake_users_db={"johndoe":{"username":"johndoe","full_name":"John Doe","email":"johndoe@example.com","hashed_password":"fakehashedsecret","disabled":False,},"alice":{"username":"alice","full_name":"Alice Wonderson","email":"alice@example.com","hashed_password":"fakehashedsecret2","disabled":True,},}app=FastAPI()deffake_hash_password(password:str):return"fakehashed"+passwordoauth2_scheme=OAuth2PasswordBearer(tokenUrl="token")classUser(BaseModel):username:stremail:str|None=Nonefull_name:str|None=Nonedisabled:bool|None=NoneclassUserInDB(User):hashed_password:strdefget_user(db,username:str):ifusernameindb:user_dict=db[username]returnUserInDB(**user_dict)deffake_decode_token(token):# This doesn't provide any security at all# Check the next versionuser=get_user(fake_users_db,token)returnuserasyncdefget_current_user(token:str=Depends(oauth2_scheme)):user=fake_decode_token(token)ifnotuser:raiseHTTPException(status_code=status.HTTP_401_UNAUTHORIZED,detail="Not authenticated",headers={"WWW-Authenticate":"Bearer"},)returnuserasyncdefget_current_active_user(current_user:User=Depends(get_current_user)):ifcurrent_user.disabled:raiseHTTPException(status_code=400,detail="Inactive user")returncurrent_user@app.post("/token")asyncdeflogin(form_data:OAuth2PasswordRequestForm=Depends()):user_dict=fake_users_db.get(form_data.username)ifnotuser_dict:raiseHTTPException(status_code=400,detail="Incorrect username or password")user=UserInDB(**user_dict)hashed_password=fake_hash_password(form_data.password)ifnothashed_password==user.hashed_password:raiseHTTPException(status_code=400,detail="Incorrect username or password")return{"access_token":user.username,"token_type":"bearer"}@app.get("/users/me")asyncdefread_users_me(current_user:User=Depends(get_current_active_user)):returncurrent_user

OAuth2PasswordRequestForm es una dependencia de clase que declara un body de formulario con:

  • Elusername.
  • Elpassword.
  • Un campo opcionalscope como un string grande, compuesto por strings separados por espacios.
  • Ungrant_type opcional.

Consejo

La especificación de OAuth2 en realidadrequiere un campogrant_type con un valor fijo depassword, peroOAuth2PasswordRequestForm no lo obliga.

Si necesitas imponerlo, utilizaOAuth2PasswordRequestFormStrict en lugar deOAuth2PasswordRequestForm.

  • Unclient_id opcional (no lo necesitamos para nuestro ejemplo).
  • Unclient_secret opcional (no lo necesitamos para nuestro ejemplo).

Información

OAuth2PasswordRequestForm no es una clase especial paraFastAPI como lo esOAuth2PasswordBearer.

OAuth2PasswordBearer hace queFastAPI sepa que es un esquema de seguridad. Así que se añade de esa manera a OpenAPI.

PeroOAuth2PasswordRequestForm es solo una dependencia de clase que podrías haber escrito tú mismo, o podrías haber declarado parámetros deForm directamente.

Pero como es un caso de uso común, se proporciona directamente porFastAPI, solo para facilitarlo.

Usa el form data

Consejo

La instance de la clase de dependenciaOAuth2PasswordRequestForm no tendrá un atributoscope con el string largo separado por espacios, en su lugar, tendrá un atributoscopes con la lista real de strings para cada scope enviado.

No estamos usandoscopes en este ejemplo, pero la funcionalidad está ahí si la necesitas.

Ahora, obtén los datos del usuario desde la base de datos (falsa), usando elusername del campo del form.

Si no existe tal usuario, devolvemos un error diciendo "Incorrect username or password".

Para el error, usamos la excepciónHTTPException:

fromtypingimportAnnotatedfromfastapiimportDepends,FastAPI,HTTPException,statusfromfastapi.securityimportOAuth2PasswordBearer,OAuth2PasswordRequestFormfrompydanticimportBaseModelfake_users_db={"johndoe":{"username":"johndoe","full_name":"John Doe","email":"johndoe@example.com","hashed_password":"fakehashedsecret","disabled":False,},"alice":{"username":"alice","full_name":"Alice Wonderson","email":"alice@example.com","hashed_password":"fakehashedsecret2","disabled":True,},}app=FastAPI()deffake_hash_password(password:str):return"fakehashed"+passwordoauth2_scheme=OAuth2PasswordBearer(tokenUrl="token")classUser(BaseModel):username:stremail:str|None=Nonefull_name:str|None=Nonedisabled:bool|None=NoneclassUserInDB(User):hashed_password:strdefget_user(db,username:str):ifusernameindb:user_dict=db[username]returnUserInDB(**user_dict)deffake_decode_token(token):# This doesn't provide any security at all# Check the next versionuser=get_user(fake_users_db,token)returnuserasyncdefget_current_user(token:Annotated[str,Depends(oauth2_scheme)]):user=fake_decode_token(token)ifnotuser:raiseHTTPException(status_code=status.HTTP_401_UNAUTHORIZED,detail="Not authenticated",headers={"WWW-Authenticate":"Bearer"},)returnuserasyncdefget_current_active_user(current_user:Annotated[User,Depends(get_current_user)],):ifcurrent_user.disabled:raiseHTTPException(status_code=400,detail="Inactive user")returncurrent_user@app.post("/token")asyncdeflogin(form_data:Annotated[OAuth2PasswordRequestForm,Depends()]):user_dict=fake_users_db.get(form_data.username)ifnotuser_dict:raiseHTTPException(status_code=400,detail="Incorrect username or password")user=UserInDB(**user_dict)hashed_password=fake_hash_password(form_data.password)ifnothashed_password==user.hashed_password:raiseHTTPException(status_code=400,detail="Incorrect username or password")return{"access_token":user.username,"token_type":"bearer"}@app.get("/users/me")asyncdefread_users_me(current_user:Annotated[User,Depends(get_current_active_user)],):returncurrent_user
🤓 Other versions and variants

Tip

Prefer to use theAnnotated version if possible.

fromfastapiimportDepends,FastAPI,HTTPException,statusfromfastapi.securityimportOAuth2PasswordBearer,OAuth2PasswordRequestFormfrompydanticimportBaseModelfake_users_db={"johndoe":{"username":"johndoe","full_name":"John Doe","email":"johndoe@example.com","hashed_password":"fakehashedsecret","disabled":False,},"alice":{"username":"alice","full_name":"Alice Wonderson","email":"alice@example.com","hashed_password":"fakehashedsecret2","disabled":True,},}app=FastAPI()deffake_hash_password(password:str):return"fakehashed"+passwordoauth2_scheme=OAuth2PasswordBearer(tokenUrl="token")classUser(BaseModel):username:stremail:str|None=Nonefull_name:str|None=Nonedisabled:bool|None=NoneclassUserInDB(User):hashed_password:strdefget_user(db,username:str):ifusernameindb:user_dict=db[username]returnUserInDB(**user_dict)deffake_decode_token(token):# This doesn't provide any security at all# Check the next versionuser=get_user(fake_users_db,token)returnuserasyncdefget_current_user(token:str=Depends(oauth2_scheme)):user=fake_decode_token(token)ifnotuser:raiseHTTPException(status_code=status.HTTP_401_UNAUTHORIZED,detail="Not authenticated",headers={"WWW-Authenticate":"Bearer"},)returnuserasyncdefget_current_active_user(current_user:User=Depends(get_current_user)):ifcurrent_user.disabled:raiseHTTPException(status_code=400,detail="Inactive user")returncurrent_user@app.post("/token")asyncdeflogin(form_data:OAuth2PasswordRequestForm=Depends()):user_dict=fake_users_db.get(form_data.username)ifnotuser_dict:raiseHTTPException(status_code=400,detail="Incorrect username or password")user=UserInDB(**user_dict)hashed_password=fake_hash_password(form_data.password)ifnothashed_password==user.hashed_password:raiseHTTPException(status_code=400,detail="Incorrect username or password")return{"access_token":user.username,"token_type":"bearer"}@app.get("/users/me")asyncdefread_users_me(current_user:User=Depends(get_current_active_user)):returncurrent_user

Revisa el password

En este punto tenemos los datos del usuario de nuestra base de datos, pero no hemos revisado el password.

Primero pongamos esos datos en el modeloUserInDB de Pydantic.

Nunca deberías guardar passwords en texto plano, así que, usaremos el sistema de hash de passwords (falso).

Si los passwords no coinciden, devolvemos el mismo error.

Hashing de passwords

"Hacer hash" significa: convertir algún contenido (un password en este caso) en una secuencia de bytes (solo un string) que parece un galimatías.

Siempre que pases exactamente el mismo contenido (exactamente el mismo password) obtienes exactamente el mismo galimatías.

Pero no puedes convertir del galimatías al password.

Por qué usar hashing de passwords

Si tu base de datos es robada, el ladrón no tendrá los passwords en texto plano de tus usuarios, solo los hashes.

Entonces, el ladrón no podrá intentar usar esos mismos passwords en otro sistema (como muchos usuarios usan el mismo password en todas partes, esto sería peligroso).

fromtypingimportAnnotatedfromfastapiimportDepends,FastAPI,HTTPException,statusfromfastapi.securityimportOAuth2PasswordBearer,OAuth2PasswordRequestFormfrompydanticimportBaseModelfake_users_db={"johndoe":{"username":"johndoe","full_name":"John Doe","email":"johndoe@example.com","hashed_password":"fakehashedsecret","disabled":False,},"alice":{"username":"alice","full_name":"Alice Wonderson","email":"alice@example.com","hashed_password":"fakehashedsecret2","disabled":True,},}app=FastAPI()deffake_hash_password(password:str):return"fakehashed"+passwordoauth2_scheme=OAuth2PasswordBearer(tokenUrl="token")classUser(BaseModel):username:stremail:str|None=Nonefull_name:str|None=Nonedisabled:bool|None=NoneclassUserInDB(User):hashed_password:strdefget_user(db,username:str):ifusernameindb:user_dict=db[username]returnUserInDB(**user_dict)deffake_decode_token(token):# This doesn't provide any security at all# Check the next versionuser=get_user(fake_users_db,token)returnuserasyncdefget_current_user(token:Annotated[str,Depends(oauth2_scheme)]):user=fake_decode_token(token)ifnotuser:raiseHTTPException(status_code=status.HTTP_401_UNAUTHORIZED,detail="Not authenticated",headers={"WWW-Authenticate":"Bearer"},)returnuserasyncdefget_current_active_user(current_user:Annotated[User,Depends(get_current_user)],):ifcurrent_user.disabled:raiseHTTPException(status_code=400,detail="Inactive user")returncurrent_user@app.post("/token")asyncdeflogin(form_data:Annotated[OAuth2PasswordRequestForm,Depends()]):user_dict=fake_users_db.get(form_data.username)ifnotuser_dict:raiseHTTPException(status_code=400,detail="Incorrect username or password")user=UserInDB(**user_dict)hashed_password=fake_hash_password(form_data.password)ifnothashed_password==user.hashed_password:raiseHTTPException(status_code=400,detail="Incorrect username or password")return{"access_token":user.username,"token_type":"bearer"}@app.get("/users/me")asyncdefread_users_me(current_user:Annotated[User,Depends(get_current_active_user)],):returncurrent_user
🤓 Other versions and variants

Tip

Prefer to use theAnnotated version if possible.

fromfastapiimportDepends,FastAPI,HTTPException,statusfromfastapi.securityimportOAuth2PasswordBearer,OAuth2PasswordRequestFormfrompydanticimportBaseModelfake_users_db={"johndoe":{"username":"johndoe","full_name":"John Doe","email":"johndoe@example.com","hashed_password":"fakehashedsecret","disabled":False,},"alice":{"username":"alice","full_name":"Alice Wonderson","email":"alice@example.com","hashed_password":"fakehashedsecret2","disabled":True,},}app=FastAPI()deffake_hash_password(password:str):return"fakehashed"+passwordoauth2_scheme=OAuth2PasswordBearer(tokenUrl="token")classUser(BaseModel):username:stremail:str|None=Nonefull_name:str|None=Nonedisabled:bool|None=NoneclassUserInDB(User):hashed_password:strdefget_user(db,username:str):ifusernameindb:user_dict=db[username]returnUserInDB(**user_dict)deffake_decode_token(token):# This doesn't provide any security at all# Check the next versionuser=get_user(fake_users_db,token)returnuserasyncdefget_current_user(token:str=Depends(oauth2_scheme)):user=fake_decode_token(token)ifnotuser:raiseHTTPException(status_code=status.HTTP_401_UNAUTHORIZED,detail="Not authenticated",headers={"WWW-Authenticate":"Bearer"},)returnuserasyncdefget_current_active_user(current_user:User=Depends(get_current_user)):ifcurrent_user.disabled:raiseHTTPException(status_code=400,detail="Inactive user")returncurrent_user@app.post("/token")asyncdeflogin(form_data:OAuth2PasswordRequestForm=Depends()):user_dict=fake_users_db.get(form_data.username)ifnotuser_dict:raiseHTTPException(status_code=400,detail="Incorrect username or password")user=UserInDB(**user_dict)hashed_password=fake_hash_password(form_data.password)ifnothashed_password==user.hashed_password:raiseHTTPException(status_code=400,detail="Incorrect username or password")return{"access_token":user.username,"token_type":"bearer"}@app.get("/users/me")asyncdefread_users_me(current_user:User=Depends(get_current_active_user)):returncurrent_user

Sobre**user_dict

UserInDB(**user_dict) significa:

Pasa las claves y valores deuser_dict directamente como argumentos clave-valor, equivalente a:

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"],)

Información

Para una explicación más completa de**user_dict revisa enla documentación paraExtra Models.

Devolver el token

El response del endpointtoken debe ser un objeto JSON.

Debe tener untoken_type. En nuestro caso, como estamos usando tokens "Bearer", el tipo de token debe ser "bearer".

Y debe tener unaccess_token, con un string que contenga nuestro token de acceso.

Para este ejemplo simple, vamos a ser completamente inseguros y devolver el mismousername como el token.

Consejo

En el próximo capítulo, verás una implementación segura real, con hashing de passwords y tokensJWT.

Pero por ahora, enfoquémonos en los detalles específicos que necesitamos.

fromtypingimportAnnotatedfromfastapiimportDepends,FastAPI,HTTPException,statusfromfastapi.securityimportOAuth2PasswordBearer,OAuth2PasswordRequestFormfrompydanticimportBaseModelfake_users_db={"johndoe":{"username":"johndoe","full_name":"John Doe","email":"johndoe@example.com","hashed_password":"fakehashedsecret","disabled":False,},"alice":{"username":"alice","full_name":"Alice Wonderson","email":"alice@example.com","hashed_password":"fakehashedsecret2","disabled":True,},}app=FastAPI()deffake_hash_password(password:str):return"fakehashed"+passwordoauth2_scheme=OAuth2PasswordBearer(tokenUrl="token")classUser(BaseModel):username:stremail:str|None=Nonefull_name:str|None=Nonedisabled:bool|None=NoneclassUserInDB(User):hashed_password:strdefget_user(db,username:str):ifusernameindb:user_dict=db[username]returnUserInDB(**user_dict)deffake_decode_token(token):# This doesn't provide any security at all# Check the next versionuser=get_user(fake_users_db,token)returnuserasyncdefget_current_user(token:Annotated[str,Depends(oauth2_scheme)]):user=fake_decode_token(token)ifnotuser:raiseHTTPException(status_code=status.HTTP_401_UNAUTHORIZED,detail="Not authenticated",headers={"WWW-Authenticate":"Bearer"},)returnuserasyncdefget_current_active_user(current_user:Annotated[User,Depends(get_current_user)],):ifcurrent_user.disabled:raiseHTTPException(status_code=400,detail="Inactive user")returncurrent_user@app.post("/token")asyncdeflogin(form_data:Annotated[OAuth2PasswordRequestForm,Depends()]):user_dict=fake_users_db.get(form_data.username)ifnotuser_dict:raiseHTTPException(status_code=400,detail="Incorrect username or password")user=UserInDB(**user_dict)hashed_password=fake_hash_password(form_data.password)ifnothashed_password==user.hashed_password:raiseHTTPException(status_code=400,detail="Incorrect username or password")return{"access_token":user.username,"token_type":"bearer"}@app.get("/users/me")asyncdefread_users_me(current_user:Annotated[User,Depends(get_current_active_user)],):returncurrent_user
🤓 Other versions and variants

Tip

Prefer to use theAnnotated version if possible.

fromfastapiimportDepends,FastAPI,HTTPException,statusfromfastapi.securityimportOAuth2PasswordBearer,OAuth2PasswordRequestFormfrompydanticimportBaseModelfake_users_db={"johndoe":{"username":"johndoe","full_name":"John Doe","email":"johndoe@example.com","hashed_password":"fakehashedsecret","disabled":False,},"alice":{"username":"alice","full_name":"Alice Wonderson","email":"alice@example.com","hashed_password":"fakehashedsecret2","disabled":True,},}app=FastAPI()deffake_hash_password(password:str):return"fakehashed"+passwordoauth2_scheme=OAuth2PasswordBearer(tokenUrl="token")classUser(BaseModel):username:stremail:str|None=Nonefull_name:str|None=Nonedisabled:bool|None=NoneclassUserInDB(User):hashed_password:strdefget_user(db,username:str):ifusernameindb:user_dict=db[username]returnUserInDB(**user_dict)deffake_decode_token(token):# This doesn't provide any security at all# Check the next versionuser=get_user(fake_users_db,token)returnuserasyncdefget_current_user(token:str=Depends(oauth2_scheme)):user=fake_decode_token(token)ifnotuser:raiseHTTPException(status_code=status.HTTP_401_UNAUTHORIZED,detail="Not authenticated",headers={"WWW-Authenticate":"Bearer"},)returnuserasyncdefget_current_active_user(current_user:User=Depends(get_current_user)):ifcurrent_user.disabled:raiseHTTPException(status_code=400,detail="Inactive user")returncurrent_user@app.post("/token")asyncdeflogin(form_data:OAuth2PasswordRequestForm=Depends()):user_dict=fake_users_db.get(form_data.username)ifnotuser_dict:raiseHTTPException(status_code=400,detail="Incorrect username or password")user=UserInDB(**user_dict)hashed_password=fake_hash_password(form_data.password)ifnothashed_password==user.hashed_password:raiseHTTPException(status_code=400,detail="Incorrect username or password")return{"access_token":user.username,"token_type":"bearer"}@app.get("/users/me")asyncdefread_users_me(current_user:User=Depends(get_current_active_user)):returncurrent_user

Consejo

De acuerdo con la especificación, deberías devolver un JSON con unaccess_token y untoken_type, igual que en este ejemplo.

Esto es algo que tienes que hacer tú mismo en tu código, y asegurarte de usar esas claves JSON.

Es casi lo único que tienes que recordar hacer correctamente tú mismo, para ser compatible con las especificaciones.

Para el resto,FastAPI lo maneja por ti.

Actualizar las dependencias

Ahora vamos a actualizar nuestras dependencias.

Queremos obtener elcurrent_usersolo si este usuario está activo.

Entonces, creamos una dependencia adicionalget_current_active_user que a su vez utilizaget_current_user como dependencia.

Ambas dependencias solo devolverán un error HTTP si el usuario no existe, o si está inactivo.

Así que, en nuestro endpoint, solo obtendremos un usuario si el usuario existe, fue autenticado correctamente, y está activo:

fromtypingimportAnnotatedfromfastapiimportDepends,FastAPI,HTTPException,statusfromfastapi.securityimportOAuth2PasswordBearer,OAuth2PasswordRequestFormfrompydanticimportBaseModelfake_users_db={"johndoe":{"username":"johndoe","full_name":"John Doe","email":"johndoe@example.com","hashed_password":"fakehashedsecret","disabled":False,},"alice":{"username":"alice","full_name":"Alice Wonderson","email":"alice@example.com","hashed_password":"fakehashedsecret2","disabled":True,},}app=FastAPI()deffake_hash_password(password:str):return"fakehashed"+passwordoauth2_scheme=OAuth2PasswordBearer(tokenUrl="token")classUser(BaseModel):username:stremail:str|None=Nonefull_name:str|None=Nonedisabled:bool|None=NoneclassUserInDB(User):hashed_password:strdefget_user(db,username:str):ifusernameindb:user_dict=db[username]returnUserInDB(**user_dict)deffake_decode_token(token):# This doesn't provide any security at all# Check the next versionuser=get_user(fake_users_db,token)returnuserasyncdefget_current_user(token:Annotated[str,Depends(oauth2_scheme)]):user=fake_decode_token(token)ifnotuser:raiseHTTPException(status_code=status.HTTP_401_UNAUTHORIZED,detail="Not authenticated",headers={"WWW-Authenticate":"Bearer"},)returnuserasyncdefget_current_active_user(current_user:Annotated[User,Depends(get_current_user)],):ifcurrent_user.disabled:raiseHTTPException(status_code=400,detail="Inactive user")returncurrent_user@app.post("/token")asyncdeflogin(form_data:Annotated[OAuth2PasswordRequestForm,Depends()]):user_dict=fake_users_db.get(form_data.username)ifnotuser_dict:raiseHTTPException(status_code=400,detail="Incorrect username or password")user=UserInDB(**user_dict)hashed_password=fake_hash_password(form_data.password)ifnothashed_password==user.hashed_password:raiseHTTPException(status_code=400,detail="Incorrect username or password")return{"access_token":user.username,"token_type":"bearer"}@app.get("/users/me")asyncdefread_users_me(current_user:Annotated[User,Depends(get_current_active_user)],):returncurrent_user
🤓 Other versions and variants

Tip

Prefer to use theAnnotated version if possible.

fromfastapiimportDepends,FastAPI,HTTPException,statusfromfastapi.securityimportOAuth2PasswordBearer,OAuth2PasswordRequestFormfrompydanticimportBaseModelfake_users_db={"johndoe":{"username":"johndoe","full_name":"John Doe","email":"johndoe@example.com","hashed_password":"fakehashedsecret","disabled":False,},"alice":{"username":"alice","full_name":"Alice Wonderson","email":"alice@example.com","hashed_password":"fakehashedsecret2","disabled":True,},}app=FastAPI()deffake_hash_password(password:str):return"fakehashed"+passwordoauth2_scheme=OAuth2PasswordBearer(tokenUrl="token")classUser(BaseModel):username:stremail:str|None=Nonefull_name:str|None=Nonedisabled:bool|None=NoneclassUserInDB(User):hashed_password:strdefget_user(db,username:str):ifusernameindb:user_dict=db[username]returnUserInDB(**user_dict)deffake_decode_token(token):# This doesn't provide any security at all# Check the next versionuser=get_user(fake_users_db,token)returnuserasyncdefget_current_user(token:str=Depends(oauth2_scheme)):user=fake_decode_token(token)ifnotuser:raiseHTTPException(status_code=status.HTTP_401_UNAUTHORIZED,detail="Not authenticated",headers={"WWW-Authenticate":"Bearer"},)returnuserasyncdefget_current_active_user(current_user:User=Depends(get_current_user)):ifcurrent_user.disabled:raiseHTTPException(status_code=400,detail="Inactive user")returncurrent_user@app.post("/token")asyncdeflogin(form_data:OAuth2PasswordRequestForm=Depends()):user_dict=fake_users_db.get(form_data.username)ifnotuser_dict:raiseHTTPException(status_code=400,detail="Incorrect username or password")user=UserInDB(**user_dict)hashed_password=fake_hash_password(form_data.password)ifnothashed_password==user.hashed_password:raiseHTTPException(status_code=400,detail="Incorrect username or password")return{"access_token":user.username,"token_type":"bearer"}@app.get("/users/me")asyncdefread_users_me(current_user:User=Depends(get_current_active_user)):returncurrent_user

Información

El header adicionalWWW-Authenticate con el valorBearer que estamos devolviendo aquí también es parte de la especificación.

Cualquier código de estado HTTP (error) 401 "UNAUTHORIZED" se supone que también debe devolver un headerWWW-Authenticate.

En el caso de tokens bearer (nuestro caso), el valor de ese header debe serBearer.

De hecho, puedes omitir ese header extra y aún funcionaría.

Pero se proporciona aquí para cumplir con las especificaciones.

Además, podría haber herramientas que lo esperen y lo usen (ahora o en el futuro) y eso podría ser útil para ti o tus usuarios, ahora o en el futuro.

Ese es el beneficio de los estándares...

Verlo en acción

Abre la documentación interactiva:http://127.0.0.1:8000/docs.

Autenticar

Haz clic en el botón "Authorize".

Usa las credenciales:

Usuario:johndoe

Contraseña:secret

Después de autenticarte en el sistema, lo verás así:

Obtener tus propios datos de usuario

Ahora usa la operaciónGET con la path/users/me.

Obtendrás los datos de tu usuario, como:

{"username":"johndoe","email":"johndoe@example.com","full_name":"John Doe","disabled":false,"hashed_password":"fakehashedsecret"}

Si haces clic en el icono de candado y cierras sesión, y luego intentas la misma operación nuevamente, obtendrás un error HTTP 401 de:

{"detail":"Not authenticated"}

Usuario inactivo

Ahora prueba con un usuario inactivo, autentícate con:

Usuario:alice

Contraseña:secret2

Y trata de usar la operaciónGET con la path/users/me.

Obtendrás un error de "Usuario inactivo", como:

{"detail":"Inactive user"}

Recapitulación

Ahora tienes las herramientas para implementar un sistema de seguridad completo basado enusername ypassword para tu API.

Usando estas herramientas, puedes hacer que el sistema de seguridad sea compatible con cualquier base de datos y con cualquier modelo de usuario o de datos.

El único detalle que falta es que en realidad no es "seguro" aún.

En el próximo capítulo verás cómo usar un paquete de hashing de passwords seguro y tokensJWT.


[8]ページ先頭

©2009-2026 Movatter.jp