【fastAPi】官方文档【OAuth2 实现密码哈希与 Bearer JWT 令牌验证】-个人理解

官方demo代码如下:

地址:https://fastapi.tiangolo.com/zh/tutorial/security/oauth2-jwt/

from datetime import datetime, timedelta
from typing import Union

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
from fastapi.staticfiles import StaticFiles

# to get a string like this run:
# openssl rand -hex 32
SECRET_KEY = "619164929f51e415d24afd5ed4c16ab6987a787b6b8281c048489bd25ad99cca"
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$WmNep8wirPPo.z0tzYr0lOZRfa3I1G6a266c.bw1HxpGjTNn5gSpe",
        "disabled": False,
    }
}


# token数据模型
class Token(BaseModel):
    access_token: str
    token_type: str


# TokenData数据模型
class TokenData(BaseModel):
    username: Union[str, None] = None


# user数据模型
class User(BaseModel):
    username: str
    email: Union[str, None] = None
    full_name: Union[str, None] = None
    disabled: Union[bool, None] = None


# user数据模型,额外加入hashed_password
class UserInDB(User):
    hashed_password: str


# CryptContext实例
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

# OAuth2PasswordBearer实例
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

app = FastAPI()
app.mount('/static', StaticFiles(directory='static'), name='static')


# 校验接收的密码是否匹配存储的哈希值
def verify_password(plain_password, hashed_password):
    return pwd_context.verify(plain_password, hashed_password)


# 获取明文密码对应的加密后的密码,(可以保存到db中,供后续验证使用)
def get_password_hash(password):
    return pwd_context.hash(password)


# 从db中找username
def get_user(db, username: str):
    if username in db:
        user_dict = db[username]
        # 解包返回符合模型的数据
        return UserInDB(**user_dict)


# 身份验证,并返回用户
def authenticate_user(fake_db, username: str, password: str):
    # 获取user数据
    user = get_user(fake_db, username)
    if not user:
        return False
    # 验证user数据中的hashed_password与传入的password是否一致
    if not verify_password(password, user.hashed_password):
        return False
    return user


# 创建jwt令牌
def create_access_token(data: dict, expires_delta: Union[timedelta, None] = None):
    # 复制传入的dict
    to_encode = data.copy()
    # 赋值expire
    if expires_delta:
        expire = datetime.utcnow() + expires_delta
    else:
        expire = datetime.utcnow() + timedelta(minutes=15)
    # 把{"exp": expire}加入字典to_encode
    to_encode.update({"exp": expire})
    # 获取加密后的jwt,传入需要加密的数据,密钥,加密算法
    encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
    return encoded_jwt


# 根据获取的user判断:返回报错信息,还是返回user
async def get_current_user(token: str = Depends(oauth2_scheme)):  # 此处依赖oauth2_scheme
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Could not validate credentials",
        headers={"WWW-Authenticate": "Bearer"},
    )
    try:
        # 对传入的token进行decode
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        # 获取username
        username: str = payload.get("sub")
        if username is None:
            raise credentials_exception
        # 校验username
        token_data = TokenData(username=username)
    except JWTError:
        raise credentials_exception
    # 从db中找username
    user = get_user(fake_users_db, username=token_data.username)
    if user is None:
        raise credentials_exception
    return user


# 根据获取user数据内的disabled值判断是否有效,并返回对应内容
async def get_current_active_user(current_user: User = Depends(get_current_user)):  # # 此处依赖get_current_user
    # 如果disabled,则返回错误信息
    if current_user.disabled:
        raise HTTPException(status_code=400, detail="Inactive user")
    return current_user


@app.post("/token", response_model=Token)  # 响应模型使用token模型
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
    # form_data依赖OAuth2PasswordRequestForm
    """
    OAuth2PasswordRequestForm 并不像 OAuth2PasswordBearer 一样是 FastAPI 的一个特殊的类。
    OAuth2PasswordBearer 使得 FastAPI 明白它是一个安全方案。所以它得以通过这种方式添加到 OpenAPI 中。
    但 OAuth2PasswordRequestForm 只是一个你可以自己编写的类依赖项,或者你也可以直接声明 Form 参数。
    但是由于这是一种常见的使用场景,因此 FastAPI 出于简便直接提供了它。
    """

    # 传入db数据,username,password,验证user
    user = authenticate_user(fake_users_db, form_data.username, form_data.password)
    # 如果验证失败,返回错误信息
    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令牌
    access_token = create_access_token(
        data={"sub": user.username}, expires_delta=access_token_expires
    )
    # 返回令牌,必须包含token_type和access_token
    return {"access_token": access_token, "token_type": "bearer"}


# 判断当前用户是否处于启用状态disabled
@app.get("/users/me/", response_model=User)  # 响应模型使用User
async def read_users_me(current_user: User = Depends(get_current_active_user)):  # 此处依赖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}]

1、注意使用get_password_hash生成密文密码后放入到db中,后续在docs中验证登陆时,使用明文密码

2、该模型稍加修改就可以直接使用了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值