from datetime import datetime, timedelta
from typing import Optional
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from jose import JWTError, jwt
from passlib.context import CryptContext
from pydantic import BaseModel
# to generate a string like this run:
# openssl rand -hex 32
SECRET_KEY = "766dadc27acd4c9b603adb9e7d4943bd0c43bbace609fad9b3bb5a4d1dc08d00H"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
fake_users_db = {
"johndoe": {
"username": "johndoe",
"full_name": "John Doe",
"email": "johndoe@example.com",
"hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW",
"disabled": False,
}
}
class Token(BaseModel):
access_token: str
token_type: str
class TokenData(BaseModel):
username: Optional[str] = None
class User(BaseModel):
username: str
email: Optional[str] = None
full_name: Optional[str] = None
disabled: Optional[bool] = None
class UserInDb(User):
hashed_password: str
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
app = FastAPI()
# 判断明文密码和加密后的密码是否相同
def verify_password(plain_password, hashed_password):
return pwd_context.verify(plain_password, hashed_password)
# 生成加密密码
def get_password_hash(password):
return pwd_context.hash(password)
def get_user(db, username: str):
if username in db:
user_dict = db[username]
return UserInDb(**user_dict) # 返回UserInDb的字典
def authenticate_user(fake_db, username: str, password: str):
user = get_user(fake_db, username) # 返回UserInDb的字典
if not user:
return False
if not verify_password(password, user.hashed_password): #比较加密后的hash值是否一致
return False
return user # 返回UserInDb的字典
# 创建token
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
to_encode = data.copy()
if expires_delta:
expire = datetime.utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=15)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) # 加密时只有一种加密算法
return encoded_jwt # 返回token
# 获取当前用户 有auth验证
# oauth2_scheme 返回的是 {"access_token": access_token, "token_type": "Bearer"}
# access_token = create_access_token({"sub": user.username}, expires_delta=access_token_expires)
async def get_current_user(token: str = Depends(oauth2_scheme)):
# 创建抛出异常提示信息
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) # 解密时可以有多种解密算法
username: str = payload.get("sub")
if username is None:
raise credentials_exception
token_data = TokenData(username=username) # TokenData只有一个username字段
except JWTError:
raise credentials_exception
user = get_user(fake_users_db, username=token_data.username) # 返回UserInDb的字典
if user is None:
raise credentials_exception
return user # 返回UserInDb的字典
# 返回当前活跃用户
async def get_current_active_user(current_user: User = Depends(get_current_user)):
# 返回的是UserInDb, 经过User类过滤后, 去掉字典中的密码,仅返回User字典
if current_user.disabled:
raise HTTPException(status_code=400, detail="Inactive user")
return current_user # 返回User的字典, 返回的字典中没有密码
@app.post("/token", response_model=Token)
async def login_for_access_token(formdata: OAuth2PasswordRequestForm = Depends()):
user = authenticate_user(fake_users_db, formdata.username, formdata.password) # 返回UserInDb的字典
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Bearer"},
)
# 设定token过期时间, 以分钟为单位
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
# 返回jwt加密后的token
access_token = create_access_token({"sub": user.username}, expires_delta=access_token_expires)
print('access_token', access_token)
return {"access_token": access_token, "token_type": "Bearer"}
@app.get("/users/me/", response_model=User)
async def read_users_me(current_user: User = Depends(get_current_active_user)):
return current_user # 返回User字典
@app.get("/users/me/items")
async def read_own_items(current_user: User = Depends(get_current_active_user)):
return [{"item_id": "Foo", "owner": current_user.username}]
参考
https://fastapi.tiangolo.com/zh/tutorial/security/oauth2-jwt/