三周精通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.rhkb.cn/news/460628.html

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

相关文章

Hugging Face | 个人使用笔记

一、网站介绍 模型和数据集都是开源的 搜索模型是默认按照趋势排序的 二、模型具体页面 三、调用API小练习 模型网站:flux-RealismLora 1.点击View Code 获取参考代码 2.创建一个python文件复制进一个代码编辑器 注意:需要补充最后一行保存代码 …

用unity XR interaction Toolkit 制作垃圾分类虚拟仿真项目

项目效果演示: 垃圾分类虚拟仿真项目演示 1.环境配置 选择universal 3D(通用渲染管道)项目(不然导入素材包会丢失材质)。 选择Window->Package Manager,安装其中的XR interaction Toolkit。 选择其中的Samples,导入Starter Assets。 选择…

基于web的便捷饭店点餐小程序的设计与实现(lw+演示+源码+运行)

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

快速入门HTML

欢迎关注个人主页:逸狼 创造不易,可以点点赞吗 如有错误,欢迎指出~ 目录 第一个html文件 标签 h1~h6 p >段落标签 br > 换行标签 img >图片标签 a >超链接标签 表格标签 表单标签 表单控件 form表单 ⽆语义标签:div&span 综…

【简道云 -注册/登录安全分析报告】

前言 由于网站注册入口容易被黑客攻击,存在如下安全问题: 暴力破解密码,造成用户信息泄露短信盗刷的安全问题,影响业务及导致用户投诉带来经济损失,尤其是后付费客户,风险巨大,造成亏损无底洞…

C#与C++交互开发系列(十七):线程安全

前言 在跨平台开发和多线程编程中,线程安全是不可忽视的重要因素。C和C#中提供了各自的线程同步机制,但在跨语言调用中,如何确保数据一致性、避免数据竞争和死锁等问题,是开发人员必须考虑的重点。 本文将介绍在C#和C交互开发中确…

docker-minio启动参数

完整命令 docker run -p 9000:9000 -p 9090:9090 -v /opt/minio/data:/data -d --name minio -d --restartalways -e "MINIO_ACCESS_KEYminio" -e "MINIO_SECRET_KEYminioadmin123" minio/minio server --console-address ":9090" -address &…

理解 CSS 中的绝对定位与 Flex 布局混用

理解 CSS 中的绝对定位与 Flex 布局混用 在现代网页设计中,CSS 布局技术如 flex 和绝对定位被广泛使用。然而,这两者结合使用时,可能会导致一些意想不到的布局问题。本文将探讨如何正确使用绝对定位元素,避免它们受到 flex 布局的…

书生大模型实战营 L0 入门岛

书生大模型训练营入门岛任务——训练营链接 1. Linux前置知识 任务:端口转发 当使用vscode远程连接服务器时,在服务器运行的任务,vscode会自动帮忙进行端口映射,方便本地进行访问。 2. Python前置知识 任务1:Leec…

网络搜索引擎Shodan(2)

声明:学习视频来自b站up主 泷羽sec,如涉及侵权马上删除文章 声明:本文主要用作技术分享,所有内容仅供参考。任何使用或依赖于本文信息所造成的法律后果均与本人无关。请读者自行判断风险,并遵循相关法律法规。 感谢泷…

Linux 练习三

1、建立用户组 shengcan,其id 为 2000 [rootlocalhost 桌面]# groupadd -g 2000 shengchan 2、建立用户组 caiwu,其id 为 2001 [rootlocalhost 桌面]# groupadd -g 2001 caiwu 3、建立用户组 jishu,其 id 为 2002 [rootlocalhost 桌面]#…

Docker Compose一键部署Spring Boot + Vue项目

目录 前提条件 概述 Compose简介 Compose文件 Compose环境 Compose命令 帮助命令 关键命令 Compose部署项目 初始化环境 查看代码文件 sql数据准备 nginx配置文件准备 创建 compose.yaml 一键启动compose多个容器 浏览器访问虚拟机ip:80(可省略默认的80端口) …

C语言 | Leetcode C语言题解之第522题最长特殊序列II

题目&#xff1a; 题解&#xff1a; #define MAX(a, b) ((a) > (b) ? (a) : (b))bool is_subseq(const char *s, const char *t) {int pt_s 0, pt_t 0;int len_s strlen(s), len_t strlen(t);while (pt_s < len_s && pt_t < len_t) {if (s[pt_s] t[pt_…

第二十三章 Vue组件通信之非父子组件通信

目录 一、引言 1.1. event bus 事件总线 1.1.1. 实现步骤 1.2. provide & inject 1.2.1. 实现步骤 二、event bus事件总线完整代码 2.1. 工程结构图 ​2.2. main.js 2.3. App.vue 2.4. EventBus.js 2.5. BaseC.vue 2.6. BaseB.vue 2.7. BaseA.vue 三、provi…

无人机之自动控制原理篇

一、飞控系统 无人机飞控是指无人机的飞行控制系统&#xff0c;是无人机的大脑。飞控系统通过传感器、控制器和执行机构三部分实现对无人机的自动控制。 传感器&#xff1a;传感器负责收集无人机的姿态、速度、高度等信息。常见的传感器包括陀螺仪、加速度计、磁力计、气压计、…

JS实现图片放大镜效果

代码&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title><styl…

链表:两数相加

目录 LeetCode2 两数相加 LeetCode445 两数相加II LeetCode2 两数相加 /*** Definition for singly-linked list.* struct ListNode {* int val;* ListNode *next;* ListNode() : val(0), next(nullptr) {}* ListNode(int x) : val(x), next(nullptr) {}* …

练习LabVIEW第二十九题

学习目标&#xff1a; 刚学了LabVIEW&#xff0c;在网上找了些题&#xff0c;练习一下LabVIEW&#xff0c;有不对不好不足的地方欢迎指正&#xff01; 第二十九题&#xff1a; 设计一评分程序&#xff0c;输入不同的分数会得到不同的评论。 分数小于60&#xff0c;“警告”指…

Unity3D 开发教程:从入门到精通

Unity3D 开发教程&#xff1a;从入门到精通 Unity3D 是一款强大的跨平台游戏引擎&#xff0c;广泛应用于游戏开发、虚拟现实、增强现实等领域。本文将详细介绍 Unity3D 的基本概念、开发流程以及一些高级技巧&#xff0c;帮助你从零基础到掌握 Unity3D 开发。 目录 Unity3D…

3.2 大数据概念、特征与价值

文章目录 大数据的概念美国高德纳咨询公司的定义麦肯锡全球研究所的定义狭义和广义的大数据 大数据的特征Volume&#xff08;体积&#xff09;Variety&#xff08;种类&#xff09;Velocity&#xff08;速度&#xff09;Value&#xff08;价值&#xff09;Veracity&#xff08;…