来源FastAPI 安全性,由于看不太懂文档的表达,故记录一下反复阅读的成果,主要围绕于示例代码做出的解释,部分理解极有可能存在偏差。
可以直接看实践指导和文档代码,看不懂再看代码说明。
安全性简介
安全-第一步
文档代码
from fastapi import Depends, FastAPI
from fastapi.security import OAuth2PasswordBearer
app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
@app.get("/items/")
async def read_items(token: str = Depends(oauth2_scheme)):
return {"token": token}
运行说明:先安装 python-multipart。安装命令:pip install python-multipart
。这是因为 OAuth2 使用表单数据发送 username 与 password。
代码说明
补充知识:形如
def function1 (param: = Depends(function2))
中function2
为依赖项,具体来说,调用function1时,function1读取function2需要的实参给function2,然后function2执行结果返回给parm,更具体可参考依赖项官方文档。
从路径函数入手,async def read_items(token: str = Depends(oauth2_scheme)):
,其中oauth2_scheme
是OAuth2PasswordBearer
的实例化对象(可调用对象),作为依赖项oauth2_scheme读取请求头中Authorization内容,获取形式如{'access_token':..., 'token_type':...}
的字典内容。而显然不做任何操作现在请求头中是不会有任何内容的。
所以样例代码运行结果为下图,显示没有权限。
我们会注意到文档页面多了一个东西Authorize。
这个是由于使用了OAuth2PasswordBearer
依赖项。它会调用tokenUrl初始化时传入的实参比如'token'
,调用该路径函数,并将结果返回作为请求头的Authorization。如果将代码改成,#ADD
注释为增加到代码。
import uvicorn
from fastapi import FastAPI, Depends
from fastapi.security import OAuth2PasswordBearer
app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
@app.get("/items/")
async def read_items(token: str = Depends(oauth2_scheme)):
return {"token": token}
@app.post("/token") # ADD
async def create_token(): # ADD
return {"access_token": "111", "token_type": "bearer"} #ADD
验证完后,发起的请求如下。
实践指导
所以OAuth2PasswordBearer
功能如下:
- 读取头文件的Authorization
- 验证Authorization符不符合规定。
- 返回
access_token
字段
获取当前用户
示例代码
from typing import Union
from fastapi import Depends, FastAPI
from fastapi.security import OAuth2PasswordBearer
from pydantic import BaseModel
app = FastAPI()
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
def fake_decode_token(token):
return User(
username=token + "fakedecoded", email="john@example.com", full_name="John Doe"
)
async def get_current_user(token: str = Depends(oauth2_scheme)):
user = fake_decode_token(token)
return user
@app.get("/users/me")
async def read_users_me(current_user: User = Depends(get_current_user)):
return current_user
代码说明
到目前为止,还没有涉及到登录的实际意义,这里可以直接跳过。
实践指导
使用密码和 Bearer 的简单 OAuth2
示例代码
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
代码说明
token
路径函数给前端赋予token。调用了OAuth2PasswordBearer
依赖项的为解释token的过程,一般解释过程就是寻找到对应的用户。
所以整体上来说,上面代码中get_current_active_user
以上都为获得前端传过来的token之后的验证解释token过程,而token
路径函数为验证用户名密码,并赋予token的过程。到目前为止还是没有安全性可言,因为我们传出去的token是数据库中存在的用户,而验证token也是直接匹配用户名,所以实际上不进行token的获取,而直接在请求头的Authorization传入用户名就可以登录。
更具体上来看,token
路径函数验证了用户名和相应的哈希密码,get_current_active_user
函数根据数据库字段判断用户是否可用,get_current_user
函数获取token,fake_decode_token
解释token,
实践指导
OAuth2 实现密码哈希与 Bearer JWT 令牌验证
原文
可直接看原文
这里才具有安全性