1.基础概念:
- FastAPI的主要特点是什么?与Flask/Django相比有什么优势?
- 请解释FastAPI中的路径操作装饰器(如@app.get)和路径操作函数
- FastAPI如何处理请求和响应?请解释Request和Response模型
astAPI特点:
- 高性能(基于Starlette和Pydantic)
- 自动生成交互式API文档(Swagger和ReDoc)
- 基于Python类型提示的数据验证
- 支持异步(async/await)
- 相比Flask: 性能更好,功能更现代
- 相比Django: 更轻量级,更适合API开发
路径操作装饰器和函数:
@app.get("/items/") # 定义GET请求处理
async def read_items():
return [{"name": "Item 1"}]
装饰器定义HTTP方法(GET/POST等)和路径,函数处理业务逻辑
2.鉴权
1.OAuth2 实现简单的 Password 和 Bearer 验证
1.启动服务
from typing import Union
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from pydantic import BaseModel
fake_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()
def fake_hash_password(password: str):
return "fakehashed" + password
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
class User(BaseModel):
username: str
email: Union[str, None] = None
full_name: Union[str, None] = None
disabled: Union[bool, None] = None
class UserInDB(User):
hashed_password: str
def get_user(db, username: str):
if username in db:
user_dict = db[username]
return UserInDB(**user_dict)
def fake_decode_token(token):
# This doesn't provide any security at all
# Check the next version
user = get_user(fake_users_db, token)
return user
async 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 credentials",
headers={"WWW-Authenticate": "Bearer"},
)
return user
async 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("/token")
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
user_dict = fake_users_db.get(form_data.username)
if not user_dict:
raise HTTPException(status_code=400, detail="Incorrect username or password")
user = UserInDB(**user_dict)
hashed_password = fake_hash_password(form_data.password)
if not hashed_password == user.hashed_password:
raise HTTPException(status_code=400, detail="Incorrect username or password")
return {"access_token": user.username, "token_type": "bearer"}
@app.get("/users/me")
async def read_users_me(current_user: User = Depends(get_current_active_user)):
return current_user
保存文件为getUser.py文件
执行命令如下
uvicorn getUser:app --reload --port 8002
2.测试服务
# 获取access-token
curl -X POST -d "username=johndoe&password=secret" http://localhost:8002/token
输出结果如下
{"access_token":"johndoe","token_type":"bearer"}
再用获取得token请求用户信息
curl -H "Authorization: Bearer johndoe" http://localhost:8002/users/me
输出结果如下
{"username":"johndoe","email":"johndoe@example.com","full_name":"John Doe","disabled":false,"hashed_password":"fakehashedsecret"}
2.OAuth2 实现密码哈希与 Bearer JWT 令牌验证
1.JWT 简介
JWT 即JSON 网络令牌(JSON Web Tokens)。
JWT 是一种将 JSON 对象编码为没有空格,且难以理解的长字符串的标准。JWT 的内容如下所示:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
JWT 字符串没有加密,任何人都能用它恢复原始信息。
但 JWT 使用了签名机制。接受令牌时,可以用签名校验令牌。
使用 JWT 创建有效期为一周的令牌。第二天,用户持令牌再次访问时,仍为登录状态。
2. 安装依赖
**安装PyJWT**
安装 PyJWT
,在 Python 中生成和校验 JWT 令牌:
pip install pyjwt
如果您打算使用类似 RSA 或 ECDSA 的数字签名算法,您应该安装加密库依赖项 pyjwt[crypto]
。
您可以在 PyJWT Installation docs 获得更多信息。
安装 passlib
pip install passlib[bcrypt]
3.启动服务
from datetime import datetime, timedelta, timezone
from typing import Annotated
import jwt
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from jwt.exceptions import InvalidTokenError
from passlib.context import CryptContext
from pydantic import BaseModel
# to get a string like this run:
# openssl rand -hex 32
SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7"
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: str | None = None
class User(BaseModel):
username: str
email: str | None = None
full_name: str | None = None
disabled: bool | None = 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)
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: timedelta | None = None):
to_encode = data.copy()
if expires_delta:
expire = datetime.now(timezone.utc) + expires_delta
else:
expire = datetime.now(timezone.utc) + timedelta(minutes=15)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
async def get_current_user(token: Annotated[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 = payload.get("sub")
if username is None:
raise credentials_exception
token_data = TokenData(username=username)
except InvalidTokenError:
raise credentials_exception
user = get_user(fake_users_db, username=token_data.username)
if user is None:
raise credentials_exception
return user
async def get_current_active_user(
current_user: Annotated[User, Depends(get_current_user)],
):
if current_user.disabled:
raise HTTPException(status_code=400, detail="Inactive user")
return current_user
@app.post("/token")
async def login_for_access_token(
form_data: Annotated[OAuth2PasswordRequestForm, Depends()],
) -> Token:
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=ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token(
data={"sub": user.username}, expires_delta=access_token_expires
)
return Token(access_token=access_token, token_type="bearer")
@app.get("/users/me/", response_model=User)
async def read_users_me(
current_user: Annotated[User, Depends(get_current_active_user)],
):
return current_user
@app.get("/users/me/items/")
async def read_own_items(
current_user: Annotated[User, Depends(get_current_active_user)],
):
return [{"item_id": "Foo", "owner": current_user.username}]
按照如下命令启动服务
uvicorn getUserJwt:app --reload --port 8002
4. 测试服务
$ curl -X POST -d "username=johndoe&password=secret" http://localhost:8002/token % Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 200 100 168 100 32 362 69 --:--:-- --:--:-- --:--:-- 431{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJqb2huZG9lIiwiZXhwIjoxNzUyNzM0MTEzfQ.FWOeEcv3lIxUmA7Z1ejbP2H5gf-EDGQm8SWUdD5UoCc","token_type":"bearer"}
再用获取到的token去请求
PC@DESKTOP-5SR7P58 MINGW64 /f/gitee/go_code (master)
$ curl -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJqb2huZG9lIiwiZXhwIjoxNzUyNzM0MTEzfQ.FWOeEcv3lIxUmA7Z1ejbP2H5gf-EDGQm8SWUdD5UoCc" http://localhost:8002/users/me
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0
PC@DESKTOP-5SR7P58 MINGW64 /f/gitee/go_code (master)
$ curl -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJqb2huZG9lIiwiZXhwIjoxNzUyNzM0MTEzfQ.FWOeEcv3lIxUmA7Z1ejbP2H5gf-EDGQm8SWUdD5UoCc" http://localhost:8002/users/me/
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 92 100 92 0 0 412 0 --:--:-- --:--:-- --:--:-- 412{"username":"johndoe","email":"johndoe@example.com","full_name":"John Doe","disabled":false}
可以看到携带正确的cookies可以请求成功