FastAPI 学习之路(二十八)使用密码和 Bearer 的简单 OAuth2

OAuth2 规定在使用(我们打算用的)「password 流程」时,客户端/用户必须将 username 和 password 字段作为表单数据发送。我们看下在我们应该去如何实现呢。

我们写一个登录接口,默认返回token和token_type

from fastapi import FastAPI, Depends, status, HTTPException
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm

from pydantic import BaseModel
from typing import Optional

oauth2_scheme =  OAuth2PasswordBearer(tokenUrl="token")

fake_db_users ={
    "mrli": {
        "username": "mrli",
        "full_name": "mrli_hanjing",
        "email": "mrli@qq.com",
        "hashed_password": "mrli",
        "disabled": False
    }
}

app = FastAPI()

def fake_hash_password(password: str):
    """模拟将密码加密"""
    return password


class User(BaseModel):
    username: str
    email: Optional[str] = None
    full_name: Optional[str] = None
    disabled: Optional[bool] = None


class UserInDB(User):
    hashed_password: str

def get_user(db_users, username: str):
    if username in db_users:
        user_dict = db_users[username]
        return UserInDB(**user_dict)

def fake_decode_token(token):
    """我们模拟返回的token值就是username,所以下面可以直接传token"""
    user = get_user(fake_db_users, token)
    return user

def get_current_user(token: str = Depends(oauth2_scheme)):
    user = fake_decode_token(token)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid authentication",
            headers={"WWW-Autehticated": "Bearer"}
        )
    return user


@app.post("/token")
def login(form_data: OAuth2PasswordRequestForm = Depends()):
    """
    校验密码
    目前我们已经从数据库中获取了用户数据,但尚未校验密码。
    让我们首先将这些数据放入 Pydantic UserInDB 模型中。
    永远不要保存明文密码,因此,我们将使用(伪)哈希密码系统。
    如果密码不匹配,我们将返回同一个错误。
    """
    user_dict = fake_db_users.get(form_data.username)
    if not user_dict:
        raise HTTPException(status_code=400, detail="Invalid username or password")
    user = UserInDB(**user_dict)
    hashed_password = fake_hash_password(form_data.password)
    if hashed_password != user.hashed_password:
        raise HTTPException(status_code=400, detail="Invalid username or password")
    return {"access_token": user.username, "token_type": "bearer"}

@app.get("/users/me")
def read_users_me(current_user: User = Depends(get_current_user)):
    return current_user


if __name__ == '__main__':
    import uvicorn
    uvicorn.run("main:app", reload=True, debug=True)

 我们测试下登录接口

接下来再测试下带认证的/users/me接口 (我们发现不能通过)

 接下来我们带上认证(校验通过,返回了我们想要的数据) 

在我们的代码中,有这样一句:

UserInDB(**user_dict)

 作用是直接将user_dict的键和值作为关键字参数传递,也就是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"],
)

假如我们把user的状态disabled改为True 

fake_db_users ={
    "mrli": {
        "username": "mrli",
        "full_name": "mrli_hanjing",
        "email": "mrli@qq.com",
        "hashed_password": "mrli",
        "disabled": False
    }
}

我们不想让disabled为True的用户获取信息,那么我们如何实现呢?

from fastapi import FastAPI, Depends, status, HTTPException
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm

from pydantic import BaseModel
from typing import Optional

oauth2_scheme =  OAuth2PasswordBearer(tokenUrl="token")

fake_db_users ={
    "mrli": {
        "username": "mrli",
        "full_name": "mrli_hanjing",
        "email": "mrli@qq.com",
        "hashed_password": "mrli",
        "disabled": True
    }
}

app = FastAPI()

def fake_hash_password(password: str):
    """模拟将密码加密"""
    return password


class User(BaseModel):
    username: str
    email: Optional[str] = None
    full_name: Optional[str] = None
    disabled: Optional[bool] = None


class UserInDB(User):
    hashed_password: str

def get_user(db_users, username: str):
    if username in db_users:
        user_dict = db_users[username]
        return UserInDB(**user_dict)

def fake_decode_token(token):
    """我们模拟返回的token值就是username,所以下面可以直接传token"""
    user = get_user(fake_db_users, token)
    return user

def get_current_user(token: str = Depends(oauth2_scheme)):
    user = fake_decode_token(token)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid authentication",
            headers={"WWW-Autehticated": "Bearer"}
        )
    return user

def get_current_active_user(current_user: User = Depends(get_current_user)):
    if current_user.disabled:
        raise HTTPException(status_code=400, detail="Inactive user")
    return current_user


@app.post("/login")
def login(form_data: OAuth2PasswordRequestForm = Depends()):
    """
    校验密码
    目前我们已经从数据库中获取了用户数据,但尚未校验密码。
    让我们首先将这些数据放入 Pydantic UserInDB 模型中。
    永远不要保存明文密码,因此,我们将使用(伪)哈希密码系统。
    如果密码不匹配,我们将返回同一个错误。
    """
    user_dict = fake_db_users.get(form_data.username)
    if not user_dict:
        raise HTTPException(status_code=400, detail="Invalid username or password")
    user = UserInDB(**user_dict)
    hashed_password = fake_hash_password(form_data.password)
    if hashed_password != user.hashed_password:
        raise HTTPException(status_code=400, detail="Invalid username or password")
    return {"access_token": user.username, "token_type": "bearer"}

@app.get("/users/me")
def read_users_me(current_user: User = Depends(get_current_active_user)):
    return current_user


if __name__ == '__main__':
    import uvicorn
    uvicorn.run("main:app", reload=True, debug=True)

其实很简单,我们就是在获取用户信息依赖的基础上增加了另一个是否是active的判断的依赖。

def get_current_active_user(current_user: User = Depends(get_current_user)):
    if current_user.disabled:
        raise HTTPException(status_code=400, detail="Inactive user")
    return current_user

@app.get("/users/me")
def read_users_me(current_user: User = Depends(get_current_active_user)):
   
    return current_user

FastAPI是一个现代、快速(高性能)的Web框架,用于构建APIs。它基于Python 3.6+类型提示,可以使用PydanticStarlette创建可靠、快速的APIs。FastAPI设计简洁且直观,易于学习使用,同时也非常注重文档的生成。 FastAPI架构通常包含以下几个核心组件: 1. 路由系统(Routing):定义了请求的URL对应的处理函数。 2. 请求处理器(Request Handlers):接收请求,并返回响应。 3. 依赖注入(Dependency Injection):用于处理请求的依赖,比如数据库连接、身份验证等。 4. 中间件(Middleware):在整个请求处理过程中提供额外功能,例如请求日志记录、跨域资源共享(CORS)等。 5. 安全性(Security):包括身份验证授权,确保只有经过验证的用户才能访问特定的API端点。 6. 数据模型(Data Models):用于数据验证序列化。 7. 转换器(Converters):将输入数据转换为程序中的数据类型,以及将程序中的数据类型转换为输出格式。 8. 错误处理(Error Handling):处理API运行时出现的错误,返回相应的HTTP状态码错误信息。 构建一个账户密码管理登陆界面,可以按照以下步骤进行: 1. 定义用户模型,包括用户名、密码等字段。 2. 创建身份验证接口,用于处理登录请求。 3. 使用异步数据库ORM(如SQLAlchemy)来管理用户数据。 4. 实现密码的加密存储(例如使用BCrypt)。 5. 实现身份验证中间件,对请求进行身份验证。 6. 根据用户身份权限设置访问控制。 7. 生成文档OpenAPI规范,用于测试自动生成客户端代码。 以下是一个简化的示例代码,展示了如何使用FastAPI创建一个基本的登录接口: ```python from fastapi import FastAPI, Depends, HTTPException, status from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm from pydantic import BaseModel from typing import Optional app = FastAPI() # 假设的用户数据库 fake_users_db = { "johndoe": { "username": "johndoe", "full_name": "John Doe", "email": "johndoe@example.com", "hashed_password": "$2b$12$K4yJZ6WZl1QaBCBb4fX9IuE4gV66p5Yd7J1s48zY2y2p2uRcV5p89C2z", "disabled": False, } } # OAuth2密码模式 oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") class Token(BaseModel): access_token: str token_type: str class User(BaseModel): username: str email: Optional[str] = None full_name: Optional[str] = None disabled: Optional[bool] = None class UserInDB(User): hashed_password: str def verify_password(plain_password, hashed_password): # 验证密码逻辑(略) pass 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 = get_user(fake_db, username) if not user: return False if not verify_password(password, user.hashed_password): return False return user def create_access_token(data: dict, expires_delta: Optional[timedelta] = None): # 创建访问令牌(略) pass @app.post("/token", response_model=Token) async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()): 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"}, ) access_token_expires = timedelta(minutes=30) access_token = create_access_token( data={"sub": user.username}, expires_delta=access_token_expires ) 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_user)): return current_user # 依赖项,用于获取当前用户 async def get_current_user(token: str = Depends(oauth2_scheme)): # 这里应该有验证令牌的逻辑,如果验证失败则抛出异常 user = get_user(fake_users_db, username="johndoe") if user is None: raise HTTPException( status_code=404, detail="User not found", ) return user # 稍后添加其他逻辑... ``` 这个示例提供了一个登录接口一个返回当前用户信息的接口。实际应用中,你需要添加数据库操作逻辑、令牌验证逻辑、权限控制逻辑等。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值