FastAPI 兼容性 依赖注入系统如此简洁的特性,让 FastAPI 可以与下列系统兼容:
前言
本博文将继续学习请求表单、请求文件,依赖项、安全校验等相关知识的学习,并结合已有的资料进行学习记录。具体如下所示:
一、请求表单
参考:FastAPI 学习之路(十六)Form表单_fastapi formdata-CSDN博客
1.1 登录案例
通过账户密码的方式实现账号登录操作。代码分为前端和后端、前端通过html+css+js实现,后端通过fastapi实现。
前端代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login Form</title>
</head>
<body>
<h2>Login Form</h2>
<form id="loginForm">
<label for="username">Username:</label>
<input type="text" id="username" name="username" required>
<br><br>
<label for="password">Password:</label>
<input type="password" id="password" name="password" required>
<br><br>
<button type="submit">Login</button>
</form>
<p id="response"></p>
<script>
// 获取表单和响应显示区域
const loginForm = document.getElementById('loginForm');
const responseParagraph = document.getElementById('response');
// 监听表单的提交事件
loginForm.addEventListener('submit', async function (event) {
event.preventDefault(); // 阻止表单提交的默认行为
// 获取表单数据
const username = document.getElementById('username').value;
const password = document.getElementById('password').value;
// 构建要发送的数据对象
const loginData = {
username: username,
password: password
};
try {
// 发送POST请求到FastAPI后端
const response = await fetch('http://127.0.0.1:8080/userlogin/requestlogindemo2', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(loginData)
});
// 处理响应
const result = await response.json();
if (response.status === 200) {
// 如果登录成功,显示成功消息
responseParagraph.innerText = 'Login Successful: ' + result.msg;
} else {
// 如果登录失败,显示错误消息
responseParagraph.innerText = 'Login Failed: ' + result.detail;
}
} catch (error) {
// 捕获和处理任何网络错误
responseParagraph.innerText = 'Error: ' + error.message;
}
});
</script>
</body>
</html>
fastapi后端代码:
from fastapi import APIRouter, HTTPException, Depends
from pydantic import BaseModel
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
router = APIRouter()
class UserLogin(BaseModel):
username: str
password: str
@router.get("/userlogin/hello")
async def userlogin_hello():
print("这是一个userlogin_hello函数的get请求,现在我想返回一个200的状态值")
# 返回状态值200 封装返回
return {"status": 200, "msg": "这是一个userlogin_hello函数的get请求,现在我想返回一个200的状态值"}
@router.get("/userlogin/requestlogin")
async def userlogin_requestlogin(username:str,password:str): # 接收用户名和密码
if username == "admin" and password == "admin":
print("当前进入到userlogin_requestlogin函数中,用户名和密码正确")
return {"status": 200, "msg": "登录成功"}
else:
print("当前进入到userlogin_requestlogin函数中,用户名和密码错误")
raise HTTPException(status_code=401, detail="用户名或密码错误")
# POST 请求,传递 JSON 或者表单数据
@router.post("/userlogin/requestlogindemo2")
async def userlogin_requestlogin_demo2(userlogin: UserLogin): # 接收 JSON 或表单数据
if userlogin.username == "admin" and userlogin.password == "admin":
print("当前进入到userlogin_requestlogindemo2函数中,用户名和密码正确")
return {"status": 200, "msg": "登录成功"}
else:
print("当前进入到userlogin_requestlogindemo2函数中,用户名和密码错误")
raise HTTPException(status_code=401, detail="用户名或密码错误")
在app.py文件中设置跨越访问,跨域访问在后面的文章中将会进行详细的说明和介绍。
# 允许跨域请求
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # 允许所有来源
allow_credentials=True,
allow_methods=["*"], # 允许所有HTTP方法
allow_headers=["*"], # 允许所有HTTP头
)
1.2 登录案例GET与POST的请求方式
前端代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--表单直接这样访问get的形式-->
<form action="http://127.0.0.1:8080/passport/user/loginget" method="get">
<input type="text" name="username" value="admin">
<input type="password" name="password" value="admin">
<button type="submit">Login</button>
</form>
<form action="http://127.0.0.1:8080/passport/user/loginpost_form" method="post">
<input type="text" name="username" value="admin">
<input type="password" name="password" value="admin">
<button type="submit">Login</button>
</form>
</body>
</html>
后端代码:
'''
我要进行接口测试,这个部分用于反馈请求信息
'''
import os
import shutil
from fastapi import APIRouter, HTTPException, Depends,Form,UploadFile,File,Request,Response
from pydantic import BaseModel
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
router = APIRouter()
class UserLogin(BaseModel):
username: str
password: str
loginuserinfo={}
@router.post("/passport/user/loginpost")
async def passport_user_login_post(userlogin: UserLogin):
if userlogin.username == "admin" and userlogin.password == "admin":
print("当前进入到passport_user_login_post函数中,用户名和密码正确")
return {"status": 200, "msg": "登录成功"}
else:
print("当前进入到passport_user_login_post函数中,用户名和密码错误")
return {"status": 401, "msg": "登录失败"}
@router.get("/passport/user/loginget")
async def passport_user_login_get(username:str,password:str,response:Response):
if username == "admin" and password == "admin":
print("当前进入到passport_user_login_get函数中,用户名和密码正确")
# 登录成功之后需要把用户信息存放到cookie中
# response = JSONResponse(content={"status": 200, "msg": "登录成功"})
# response.set_cookie(key="username", value=username)
# 由于是本地,因此用字典模仿一下
loginuserinfo["username"] = username
return {"status": 200, "msg": "登录成功"}
else:
print("当前进入到passport_user_login_get函数中,用户名和密码错误")
return {"status": 401, "msg": "登录失败"}
@router.post("/passport/user/loginpost_form")
async def passport_user_login_post_form(username: str = Form(...), password: str = Form(...)):
if username == "admin" and password == "admin":
print("当前进入到passport_user_login_post_form函数中,用户名和密码正确")
return {"status": 200, "msg": "登录成功"}
else:
print("当前进入到passport_user_login_post_form函数中,用户名和密码错误")
return {"status": 401, "msg": "登录失败"}
效果如图所示:
二、请求文件
FastAPI 学习之路(十七)上传文件_fastapi 上传文件-CSDN博客
FastAPI 学习之路(十八)表单与文件_fastapi formdata-CSDN博客
前端代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>用户上传文件</title>
</head>
<body>
<form action="http://127.0.0.1:8080/passport/upload" method="POST" enctype="multipart/form-data">
<input type="file" name="file">
<button type="submit">Upload</button>
</form>
</body>
</html>
后端代码:
@router.post("/passport/upload")
async def passport_upload_file(file: UploadFile = File(...)):
# 创建保存上传文件的目录
UPLOAD_DIR = "uploaded_files"
if not os.path.exists(UPLOAD_DIR):
os.makedirs(UPLOAD_DIR)
print("当前进入到passport_upload_file函数中,上传文件成功")
# 构建保存文件的路径
file_location = os.path.join(UPLOAD_DIR, file.filename)
# 将文件保存到服务器
with open(file_location, "wb") as buffer:
shutil.copyfileobj(file.file, buffer)
contents = await file.read()
buffer.close()
return {"filename": file.filename,"content_size": len(contents),"content_type": file.content_type}
效果图
三、依赖项
3.1 请求体更新数据
FastAPI 学习之路(二十一)请求体 - 更新数据_fastapi更新数据-CSDN博客
我们都知道,去创建请求体,更新数据我们用PUT请求,我们去试着更新下数据。
我们有一组数据,我们要更新描述。
# 请求体更新数据的学习
from fastapi import FastAPI
from typing import List, Optional
from fastapi.encoders import jsonable_encoder
from pydantic import BaseModel
from fastapi import APIRouter
router = APIRouter()
class Item(BaseModel):
name: Optional[str] = None
description: Optional[str] = None
price: Optional[float] = None
tax: float = 10.5
tags: List[str] = []
items = {
"one": {
"name": "apple", "price": 50.3
}
}
@router.put("/demo8/items", response_model=Item)
def upadte_item(name: str, item: Item):
update_item_encoded = jsonable_encoder(item)
items[name] = update_item_encoded
print(items)
return update_item_encoded
@router.get("/demo8/items/{item_id}", response_model=Item)# 返回的类型模型Item
def read_item(item_id: str):
return items[item_id]
3.2 什么是依赖注入?
FastAPI 提供了简单易用,但功能强大的依赖注入系统。这个依赖系统设计的简单易用,可以让开发人员轻松地把组件集成至 FastAPI。
声明代码(本文中为路径操作函数 )运行所需的,或要使用的「依赖」的一种方式。
然后,由系统(本文中为 FastAPI)负责执行任意需要的逻辑,为代码提供这些依赖(注入依赖项)。
依赖注入常用于以下场景:
共享业务逻辑(复用相同的代码逻辑)
共享数据库连接
实现安全、验证、角色权限
等……
上述场景均可以使用依赖注入,将代码重复最小化。
接下来,我们学习一个非常简单的例子,尽管它过于简单,不过很实用。
# 依赖项
from fastapi import APIRouter
from fastapi import FastAPI, Depends
from typing import Optional
router = APIRouter()
def common_params(q: Optional[str] = None, skip: int = 0, limit: int = 10):
return {"q": q, "skip": skip, "limit": limit}
@router.get("/demo9/items")
def read_items(commons: dict = Depends(common_params)):
return commons
@router.get("/demo9/users")
def read_users(commons: dict = Depends(common_params)):
return commons
依赖项函数的形式和结构与路径操作函数一样。
因此,可以把依赖项当作没有「装饰器」(即,没有 @app.get("/path") )的路径操作函数。
依赖项可以返回各种内容。
本例中的依赖项预期接收如下参数:
类型为 str 的可选查询参数 q
类型为 int 的可选查询参数 skip,默认值是 0
类型为 int 的可选查询参数 limit,默认值是 100
然后,依赖项函数返回包含这些值的 dict。
接收到新的请求时,FastAPI 执行如下操作:
用正确的参数调用依赖项函数(「可依赖项」)
获取函数返回的结果
把函数返回的结果赋值给路径操作函数的参数
FastAPI 兼容性 依赖注入系统如此简洁的特性,让 FastAPI 可以与下列系统兼容:
关系型数据库
NoSQL 数据库
外部支持库
外部 API
认证和鉴权系统
API 使用监控系统
响应数据注入系统
3.3 子依赖
FastAPI 学习之路(二十四)子依赖项_from fastapi import fastapi, depends-CSDN博客
FastAPI支持创建含子依赖项的依赖项。并且,可以按需声明任意深度的子依赖项嵌套层级。
FastAPI负责处理解析不同深度的子依赖项。
我们去实现一个简单的demo
from fastapi import FastAPI, Depends
from typing import Optional
app = FastAPI()
fake_db_items = [{"city": "beijing"}, {"city": "shanghai"}, {"city": "guangzhou"}]
def query_extractor(desc: Optional[str] = None):
return desc
def extend_query(desc: str = Depends(query_extractor), name: Optional[str] = ""):
if not desc:
return name
return desc
@app.get("/items")
def read_items(query: str = Depends(extend_query)):
return query
if __name__ == '__main__':
import uvicorn
uvicorn.run("main:app", reload=True, debug=True)
3.4 路径操作装饰器依赖项
有时,我们并不需要在路径操作函数中使用依赖项的返回值。或者说,有些依赖项不返回值。
但仍要执行或解析该依赖项。对于这种情况,不必在声明路径操作函数的参数时使用 Depends,而是可以在路径操作装饰器中添加一个由 dependencies 组成的 list。
我们看下,如何去实现。我们去校验下请求头中的token,请求的key。
from fastapi import FastAPI, Depends, Header, HTTPException
from typing import Optional
app = FastAPI()
fake_db_items = [{"city": "beijing"}, {"city": "shanghai"}, {"city": "guangzhou"}]
def verify_token(token: str = Header(...)):
if token != "mrli_token":
raise HTTPException(status_code=400, detail="token is invalid")
def verify_key(key: str = Header(...)):
if key != "mrli_key":
raise HTTPException(status_code=400, detail="key is invalid")
return key
@app.get("/items", dependencies=[Depends(verify_token), Depends(verify_key)])
def read_items():
return fake_db_items
if __name__ == '__main__':
import uvicorn
uvicorn.run("demo10_Depends:app", reload=True)
不传入token的时候
传入参数
3.5 全局依赖项
FastAPI 学习之路(二十六)全局依赖项_fastapi中间件验证token-CSDN博客
有时,我们要为整个应用添加依赖项。通过与定义FastAPI 学习之路(二十五)路径操作装饰器依赖项
类似的方式,可以把依赖项添加至整个 FastAPI 应用。那么我们看下,如何去实现,比如我们全局都需要校验token。我们去看下,我们应该如何实现代码。
from fastapi import FastAPI, Depends, Header, HTTPException
from typing import Optional
fake_db_items = [{"city": "beijing"}, {"city": "shanghai"}, {"city": "guangzhou"}]
def verify_token(token: str = Header(...)):
if token != "mrli_token":
raise HTTPException(status_code=400, detail="token is invalid")
app = FastAPI(dependencies=[Depends(verify_token)])
@app.get("/items")
def read_items():
return fake_db_items
@app.get("/users")
def read_users():
return "this is test return user"
if __name__ == '__main__':
import uvicorn
uvicorn.run("main:app", reload=True, debug=True)
四、安全校验
4.1 安全校验基础知识
FastAPI 学习之路(二十七)安全校验_fastapi token校验-CSDN博客
你写API接口肯定你是希望是有权限的人才能访问,没有权限的人是不能访问的,那么我们应该如何去处理呢,我们可以用的验证方式有很多,我们这次分享的是用:OAuth2来认证。那么我们看下,需要怎么才能实现呢。我们现在的接口有一部分需要用OAuth2认证后才可以访问,另一部分可以随便去访问的,那么我们看下,我们应该如何去实现
需求:1.items接口任意都可以访问
2.item接口需要认证后才可以访问。
from fastapi import FastAPI, Depends
from fastapi.security import OAuth2PasswordBearer
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
app = FastAPI()
fake_db_items = [{"city": "beijing"}, {"city": "shanghai"}, {"city": "guangzhou"}]
@app.get("/items")
def read_items():
return fake_db_items
@app.get("/item")
def read_users(city: str, token: str = Depends(oauth2_scheme)):
for item in fake_db_items:
if item["city"] == city:
return item
return {"msg": "not exist"}
if __name__ == '__main__':
import uvicorn
uvicorn.run("demo11_OAuth2:app", reload=True)
其实基于OAuth2来做处理其实是很简单的,我们只是写了一个简单的demo。我们并未实现自己的token值,也没有做真正的校验,接下来我们会慢慢深入
4.2 使用密码和 Bearer 的简单 OAuth2
FastAPI 学习之路(二十八)使用密码和 Bearer 的简单 OAuth2_fastapi bearer-CSDN博客
OAuth2 规定在使用(我们打算用的)「password 流程」时,客户端/用户必须将 username 和 password 字段作为表单数据发送。我们看下在我们应该去如何实现呢。
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("demo12_OAuth2:app", reload=True)
4.3 展示登录案例
代码如下所示:
# app.py
from fastapi import FastAPI, Depends, HTTPException
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from pydantic import BaseModel
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
# 允许跨域请求
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # 允许所有来源
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# 假设的用户数据库
fake_users_db = {
"testuser": {
"username": "testuser",
"hashed_password": "fakehashedpassword"
}
}
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
# 获取令牌
@app.post("/token")
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
'''
OAuth2PasswordRequestForm:
作用:它是一个表单模型,用于接收用户登录时提交的用户名和密码。
工作原理:在使用此类时,FastAPI 会自动解析请求中的表单数据,并将其转化为
OAuth2PasswordRequestForm 实例,使你能够方便地访问 username 和 password 字段。
用法实例:
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
username = form_data.username
password = form_data.password
# 进行用户验证
'''
print(f"输出form_data数据信息:{form_data}")
print(f"输出form_data.username数据信息:{form_data.username}")
print(f"输出form_data.password数据信息:{form_data.password}")
user = fake_users_db.get(form_data.username)# 根据用户名获取用户信息,模拟从数据库中获取用户信息
if not user or form_data.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(token: str = Depends(oauth2_scheme)):
user = fake_users_db.get(token)
print(f"输出token数据信息:{token}")
print(f"输出user数据信息:{user}")
if not user:
raise HTTPException(status_code=401, detail="Invalid credentials")
print(f"输出user['username']数据信息:{user['username']}")
return {"username": user['username']}
if __name__ == '__main__':
import uvicorn
uvicorn.run("demo13_OAuth:app", reload=True)
前端代码如下所示:
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login Example</title>
</head>
<body>
<h1>Login</h1>
<form id="loginForm">
<label for="username">Username:</label>
<input type="text" id="username" name="username" required>
<br>
<label for="password">Password:</label>
<input type="password" id="password" name="password" required>
<br>
<button type="submit">Login</button>
</form>
<pre id="response"></pre>
<script>
document.getElementById('loginForm').addEventListener('submit', async (event) => {
event.preventDefault();
const username = document.getElementById('username').value;
const password = document.getElementById('password').value;
const response = await fetch('http://127.0.0.1:8000/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams({
username: username,
password: password
})
});
const data = await response.json();
document.getElementById('response').textContent = JSON.stringify(data, null, 2);
});
</script>
</body>
</html>
postman接口访问
OAuth2PasswordBearer
-
作用:它是一个依赖项,帮助你从请求中提取 Bearer 令牌。
-
工作原理:当用户访问需要认证的路由时,它会从请求的
Authorization
头中提取 Bearer 令牌。这个令牌通常是在用户登录后生成的,并在后续请求中用于身份验证。
OAuth2PasswordRequestForm
-
作用:它是一个表单模型,用于接收用户登录时提交的用户名和密码。
-
工作原理:在使用此类时,FastAPI 会自动解析请求中的表单数据,并将其转化为
OAuth2PasswordRequestForm
实例,使你能够方便地访问username
和password
字段。
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
username = form_data.username
password = form_data.password
# 进行用户验证
参考文献
FastAPI 学习之路(十六)Form表单_fastapi formdata-CSDN博客
FastAPI 学习之路(十七)上传文件_fastapi 上传文件-CSDN博客
FastAPI 学习之路(十八)表单与文件_fastapi formdata-CSDN博客
FastAPI 学习之路(十九)处理错误/异常_fastapi 异常-CSDN博客
FastAPI 学习之路(二十一)请求体 - 更新数据_fastapi更新数据-CSDN博客
FastAPI 学习之路(二十三)用类作为依赖的注入_fastapi 依赖注入-CSDN博客
FastAPI 学习之路(二十四)子依赖项_from fastapi import fastapi, depends-CSDN博客
FastAPI 学习之路(二十五)路径操作装饰器依赖项_fastapi 路径操作装饰器-CSDN博客
FastAPI 学习之路(二十六)全局依赖项_fastapi中间件验证token-CSDN博客
FastAPI 学习之路(二十七)安全校验_fastapi token校验-CSDN博客
FastAPI 学习之路(二十八)使用密码和 Bearer 的简单 OAuth2_fastapi bearer-CSDN博客