三周精通FastAPI:24 OAuth2 实现简单的 Password 和 Bearer 验证

官网文档:https://fastapi.tiangolo.com/zh/tutorial/security/simple-oauth2/

OAuth2 实现简单的 Password 和 Bearer 验证¶

本章添加上一章示例中欠缺的部分,实现完整的安全流。

获取 username 和 password

首先,使用 FastAPI 安全工具获取 username 和 password

OAuth2 规范要求使用密码流时,客户端或用户必须以表单数据形式发送 username 和 password 字段。

并且,这两个字段必须命名为 username 和 password ,不能使用 user-name 或 email 等其它名称。

不过也不用担心,前端仍可以显示终端用户所需的名称。

数据库模型也可以使用所需的名称。

但对于登录路径操作,则要使用兼容规范的 username 和 password,(例如,实现与 API 文档集成)。

该规范要求必须以表单数据形式发送 username 和 password,因此,不能使用 JSON 对象。

Scope(作用域)¶

OAuth2 还支持客户端发送scope表单字段。

虽然表单字段的名称是 scope(单数),但实际上,它是以空格分隔的,由多个scope组成的长字符串。

作用域只是不带空格的字符串。

常用于声明指定安全权限,例如:

  • 常见用例为,users:read 或 users:write
  • 脸书和 Instagram 使用 instagram_basic
  • 谷歌使用 https://www.googleapis.com/auth/drive

"说明"

OAuth2 中,作用域只是声明指定权限的字符串。

是否使用冒号 : 等符号,或是不是 URL 并不重要。

这些细节只是特定的实现方式。

对 OAuth2 来说,都只是字符串而已。

获取 username 和 password 的代码¶

接下来,使用 FastAPI 工具获取用户名与密码。

from typing import Unionfrom fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from pydantic import BaseModelfake_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" + passwordoauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")class User(BaseModel):username: stremail: Union[str, None] = Nonefull_name: Union[str, None] = Nonedisabled: Union[bool, None] = Noneclass UserInDB(User):hashed_password: strdef 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 versionuser = get_user(fake_users_db, token)return userasync 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 userasync 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

OAuth2PasswordRequestForm

首先,导入 OAuth2PasswordRequestForm,然后,在 /token 路径操作 中,用 Depends 把该类作为依赖项。

from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm@app.post("/token")
async def login(form_data: OAuth2PasswordRequestForm = Depends()):

OAuth2PasswordRequestForm 是用以下几项内容声明表单请求体的类依赖项:

  • username
  • password
  • 可选的 scope 字段,由多个空格分隔的字符串组成的长字符串
  • 可选的 grant_type

"提示"

实际上,OAuth2 规范要求 grant_type 字段使用固定值 password,但 OAuth2PasswordRequestForm 没有作强制约束。

如需强制使用固定值 password,则不要用 OAuth2PasswordRequestForm,而是用 OAuth2PasswordRequestFormStrict

  • 可选的 client_id(本例未使用)
  • 可选的 client_secret(本例未使用)

"说明"

OAuth2PasswordRequestForm 与 OAuth2PasswordBearer 一样,都不是 FastAPI 的特殊类。

FastAPI 把 OAuth2PasswordBearer 识别为安全方案。因此,可以通过这种方式把它添加至 OpenAPI。

但 OAuth2PasswordRequestForm 只是可以自行编写的类依赖项,也可以直接声明 Form 参数。

但由于这种用例很常见,FastAPI 为了简便,就直接提供了对它的支持。

使用表单数据¶

"提示"

OAuth2PasswordRequestForm 类依赖项的实例没有以空格分隔的长字符串属性 scope,但它支持 scopes 属性,由已发送的 scope 字符串列表组成。

本例没有使用 scopes,但开发者也可以根据需要使用该属性。

现在,即可使用表单字段 username,从(伪)数据库中获取用户数据。

如果不存在指定用户,则返回错误消息,提示用户名或密码错误。本例使用 HTTPException 异常显示此错误:

from fastapi import Depends, FastAPI, HTTPException, status@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")

校验密码¶

至此,我们已经从数据库中获取了用户数据,但尚未校验密码。

接下来,首先将数据放入 Pydantic 的 UserInDB 模型。

注意:永远不要保存明文密码,本例暂时先使用(伪)哈希密码系统。

如果密码不匹配,则返回与上面相同的错误。

密码哈希¶

哈希是指,将指定内容(本例中为密码)转换为形似乱码的字节序列(其实就是字符串)。

每次传入完全相同的内容(比如,完全相同的密码)时,得到的都是完全相同的乱码。

但这个乱码无法转换回传入的密码。

为什么使用密码哈希¶

原因很简单,假如数据库被盗,窃贼无法获取用户的明文密码,得到的只是哈希值。这样一来,窃贼就无法在其它应用中使用窃取的密码,要知道,很多用户在所有系统中都使用相同的密码,风险超大。

    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")
关于 **user_dict

UserInDB(**user_dict) 是指:

直接把 user_dict 的键与值当作关键字参数传递,等效于:

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_dict 的说明,详见更多模型一章。

返回 Token¶

token 端点的响应必须是 JSON 对象。

响应返回的内容应该包含 token_type。本例中用的是BearerToken,因此, Token 类型应为bearer

返回内容还应包含 access_token 字段,它是包含权限 Token 的字符串。

本例只是简单的演示,返回的 Token 就是 username,但这种方式极不安全。

"提示"

下一章介绍使用哈希密码和 JWT Token 的真正安全机制。

但现在,仅关注所需的特定细节。

 
@app.post("/token")
async def login(form_data: OAuth2PasswordRequestForm = Depends()):......return {"access_token": user.username, "token_type": "bearer"}

"提示"

按规范的要求,应像本示例一样,返回带有 access_token 和 token_type 的 JSON 对象。

这是开发者必须在代码中自行完成的工作,并且要确保使用这些 JSON 的键。

这几乎是唯一需要开发者牢记在心,并按规范要求正确执行的事。

FastAPI 则负责处理其它的工作。

更新依赖项¶

接下来,更新依赖项。

使之仅在当前用户为激活状态时,才能获取 current_user

为此,要再创建一个依赖项 get_current_active_user,此依赖项以 get_current_user 依赖项为基础。

如果用户不存在,或状态为未激活,这两个依赖项都会返回 HTTP 错误。

因此,在端点中,只有当用户存在、通过身份验证、且状态为激活时,才能获得该用户:

 
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 userasync 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

"说明"

此处返回值为 Bearer 的响应头 WWW-Authenticate 也是规范的一部分。

任何 401UNAUTHORIZEDHTTP(错误)状态码都应返回 WWW-Authenticate 响应头。

本例中,因为使用的是 Bearer Token,该响应头的值应为 Bearer

实际上,忽略这个附加响应头,也不会有什么问题。

之所以在此提供这个附加响应头,是为了符合规范的要求。

说不定什么时候,就有工具用得上它,而且,开发者或用户也可能用得上。

这就是遵循标准的好处……

实际效果¶

打开 API 文档:http://127.0.0.1:8000/docs。

身份验证¶

点击Authorize按钮。

使用以下凭证:

用户名:johndoe

密码:secret

通过身份验证后,显示下图所示的内容:

获取当前用户数据¶

使用 /users/me 路径的 GET 操作。

可以提取如下当前用户数据:

{ "username": "johndoe", "email": "johndoe@example.com", "full_name": "John Doe", "disabled": false, "hashed_password": "fakehashedsecret" } 
 

点击小锁图标,注销后,再执行同样的操作,则会得到 HTTP 401 错误:

{ "detail": "Not authenticated" } 

未激活用户¶

测试未激活用户,输入以下信息,进行身份验证:

用户名:alice

密码:secret2

然后,执行 /users/me 路径的 GET 操作。

显示下列未激活用户错误信息:

{ "detail": "Inactive user" } 

小结¶

使用本章的工具实现基于 username 和 password 的完整 API 安全系统。

这些工具让安全系统兼容任何数据库、用户及数据模型。

唯一欠缺的是,它仍然不是真的安全

下一章,介绍使用密码哈希支持库与 JWT 令牌实现真正的安全机制。

实践

源代码

将代码存入secritybearer.py

from typing import Unionfrom fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from pydantic import BaseModelfake_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" + passwordoauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")class User(BaseModel):username: stremail: Union[str, None] = Nonefull_name: Union[str, None] = Nonedisabled: Union[bool, None] = Noneclass UserInDB(User):hashed_password: strdef 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 versionuser = get_user(fake_users_db, token)return userasync 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 userasync 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

启动服务

uvicorn securitybearer:app --reload

测试

在激活的情况下:

curl -X 'GET' 'http://127.0.0.1:8000/users/me' -H 'accept: application/json' -H 'Authorization: Bearer johndoe'

返回信息:

{"username":"johndoe","email":"johndoe@example.com","full_name":"John Doe","disabled":false,"hashed_password":"fakehashedsecret"}

换用普通账户

curl -X 'GET' 'http://127.0.0.1:8000/users/me' -H 'accept: application/json' -H 'Authorization: Bearer alice'

则会返回拒绝:

{"detail":"Inactive user"} 

当然,现在测试中,只对用户进行了验证,还不安全。后面会用OAuth2 实现密码哈希与 Bearer JWT 令牌验证。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/58537.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

字节青训-兔群繁殖之谜

问题描述 生物学家小 R 正在研究一种特殊的兔子品种的繁殖模式。这种兔子的繁殖遵循以下规律: 每对成年兔子每个月会生育一对新的小兔子(一雌一雄)。新生的小兔子需要一个月成长,到第二个月才能开始繁殖。兔子永远不会死亡。 小 R…

MiniWord

1.nuget 下载配置 2.引用 3. var value = new Dictionary<string, object>() { ["nianfen"] = nianfen, ["yuefen"] = yuefen, ["yuefenjian1"] = (int.Par…

计算机毕业设计Python+大模型恶意木马流量检测与分类 恶意流量监测 随机森林模型 深度学习 机器学习 数据可视化 大数据毕业设计 信息安全 网络安全

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; Python大模型恶意木马流量检…

04.DDD与CQRS

学习视频来源&#xff1a;DDD独家秘籍视频合集 https://space.bilibili.com/24690212/channel/collectiondetail?sid1940048&ctype0 文章目录 定义职责分离DDD与CQRS的关系领域模型和查询模型特点命令场景的领域模型查询场景的查询模型 架构方案领域事件方案1&#xff1a…

专业140+总分410+武汉大学807信号与系统考研经验武大原936电子信息与通信工程,真题,大纲,参考书。

考研专业课807信号与系统(原936)140&#xff0c;总分410&#xff0c;顺利被武汉大学录取&#xff0c;群 里不少同学希望总结一下复习经验&#xff0c;回看这一年有得有失&#xff0c;总结一下希望给大家有些参考。考研还需从自身情况出发&#xff0c;制定适合自己的复习计划&am…

eclipse下载与安装(汉化教程)超详细

目录 一、下载eclipse安装包 三、配置eclipse 代码自动补全功能 安装汉化包 中英文切换 四、用eclipse写hello world 一、下载eclipse安装包 1、首先进入 eclipse官网 如下&#xff1a; 2、这里面有很多版本&#xff1b;我们小白一般选择第二个&#xff0c;向下滑动&…

【Kettle的安装与使用】使用Kettle实现mysql和hive的数据传输(使用Kettle将mysql数据导入hive、将hive数据导入mysql)

文章目录 一、安装1、解压2、修改字符集3、启动 二、实战1、将hive数据导入mysql2、将mysql数据导入到hive 一、安装 Kettle的安装包在文章结尾 1、解压 在windows中解压到一个非中文路径下 2、修改字符集 修改 spoon.bat 文件 "-Dfile.encodingUTF-8"3、启动…

RHCE笔记-DNS服务器

一.DNS简介 DNS&#xff08;域名系统&#xff09;是一种互联网服务&#xff0c;负责将我们熟悉的域名&#xff08;比如 www.example.com&#xff09;转换为计算机能理解的IP地址&#xff08;比如 192.0.2.1&#xff09;。这样&#xff0c;当你在浏览器中输入网址时&#xff0c;…

利用QGIS工具手动绘制线轨迹并生成地理信息geojson文件

前端想要获得一个完整的shp文件或者geojson的地理信息文件&#xff0c;可以利用QGIS工具手动绘制你想要的数据点位&#xff0c;然后导出图层生成对应的文件即可。 1、新建临时图层 选择线图层&#xff0c;点击ok创建临时图层。 2、绘制线图层 在工具栏中选择添加线要素&#…

面试记录(1)

java中的抽象类和接口的区别&#xff1a; 相同点 (1) 都可以被继承 (2) 都不能被实例化 (3) 都可以包含方法声明 (4) 派生类必须实现未实现的方法 不同点 1.关键字不同&#xff1a; ​ ① 继承抽象类的关键字是extends&#xff0c;而实现接口的关键字是implements&#xff1b;…

构建您自己的 RAG 应用程序:使用 Ollama、Python 和 ChromaDB 在本地设置 LLM 的分步指南

在数据隐私至关重要的时代&#xff0c;建立自己的本地语言模型 &#xff08;LLM&#xff09; 为公司和个人都提供了至关重要的解决方案。本教程旨在指导您完成使用 Ollama、Python 3 和 ChromaDB 创建自定义聊天机器人的过程&#xff0c;所有这些机器人都托管在您的系统本地。以…

聊聊Web3D 发展趋势

随着 Web 技术的不断演进&#xff0c;Web3D 正逐渐成为各行业数字化的重要方向。Web3D 是指在网页中展示 3D 内容的技术集合。近年来&#xff0c;由于 WebGL、WebGPU 等技术的发展&#xff0c;3D 内容已经能够直接在浏览器中渲染&#xff0c;为用户提供更加沉浸、互动的体验。以…

电科金仓(人大金仓)更新授权文件(致命错误: XX000: License file expired.)

问题:电科金仓(人大金仓)数据库链接异常,重启失败,查看日志如下: 致命错误: XX000: License file expired. 位置: PostmasterMain, postmaster.c:725 解决方法: 一、下载授权文件 根据安装版本在官网下载授权文件(电科金仓-成为世界卓越的数据库产品与服务提供商)…

uniapp的video视频属性打包app后层级过高

问题&#xff1a;在使用uniapp开发APP时&#xff0c;使用video标签显示视频发现H5可以正常展示&#xff0c;但是打包到APP后&#xff0c;它的层级过高&#xff0c;把底部导航都盖住了。 官网说明&#xff1a;uni-app官网 官网给了cover-view组件或plus.nativeObj.view、subNVue…

考研资料分享系统的设计与实现(lw+演示+源码+运行)

摘 要 互联网发展至今&#xff0c;无论是其理论还是技术都已经成熟&#xff0c;而且它广泛参与在社会中的方方面面。它让信息都可以通过网络传播&#xff0c;搭配信息管理工具可以很好地为人们提供服务。针对高校教师成果信息管理混乱&#xff0c;出错率高&#xff0c;信息安全…

[perl] 数组与哈希

数组变量以 符号开始&#xff0c;元素放在括号内 简单举例如下 #!/usr/bin/perl names ("a1", "a2", "a3");print "\$names[0] $names[0]\n"; print "size: ",scalar names,"\n";$new_names shift(names); …

项目符合行业安全标准的必要步骤与实用建议

要保障项目符合行业安全标准&#xff0c;关键在于建立全面的安全管理体系、定期进行风险评估、持续培训员工&#xff0c;以及确保合规性文件和审核流程完整。例如&#xff0c;通过建立合规文件和审核流程&#xff0c;可以系统性地跟踪项目的安全实践和合规性&#xff0c;使安全…

小米15和小米15 Pro区别没那么大,但也得看准再下手

小米15和小米15 Pro区别大总结 接下来&#xff0c;我们将从关键差别等多个方面来分析两个机型的具体区别&#xff08;Ps&#xff1a;只聊不一样的&#xff0c;没提到就是一样的&#xff09;&#xff1a; 关键差别 • 屏幕素质&#xff1a;小米15采用的是6.36英寸1.5K&#xf…

【科研绘图】3DMAX管状图表生成插件TubeChart使用方法

3DMAX管状图表生成插件TubeChart&#xff0c;一款用于制作3D管状图表的工具。可以自定义切片的数量以及随机或指定切片颜色。 【版本要求】 3dMax 2008及更高版本 【安装方法】 TubeChart插件无需安装&#xff0c;使用时直接拖动插件脚本文件到3dMax视口中打开即可&#xff0…

力扣hot100-->递归/回溯

目录 递归/回溯 1. 17. 电话号码的字母组合 2. 22. 括号生成 3. 39. 组合总和 4. 46. 全排列 5. 78. 子集 递归/回溯 1. 17. 电话号码的字母组合 中等 给定一个仅包含数字 2-9 的字符串&#xff0c;返回所有它能表示的字母组合。答案可以按 任意顺序 返回。 给出数字到…