FastAPI(58)- 使用 OAuth2PasswordBearer 的简单栗子
背景
- 假设在某个域中拥有后端 API(127.0.0.1:8080)
- 并且在另一个域或同一域的不同路径(或移动应用程序)中有一个前端(127.0.0.1:8081)
- 并且希望有一种方法让前端使用用户名和密码与后端进行身份验证
- 可以使用 OAuth2 通过 FastAPI 来构建它,通过 FastAPI 提供的工具来处理安全性
OAuth2 的授权模式
- 授权码授权模式 Authorization Code Grant
- 隐式授权模式 Implicit Grant
- 密码授权模式 Resource Owner Password Credentials Grant
- 客户端凭证授权模式 Client Credentials Grant
这里讲 FastAPI 的是第三种
密码授权模式的简易流程图

- 用户在客户端输入用户名、密码
- 客户端携带用户名、密码去请求授权服务器,访问获取 token 的接口
- 授权服务器验证用户名、密码(身份验证)
- 验证通过后,返回这个用户的 token 到客户端
- 客户端存储 token,在后续发送请求携带该 token,就能通过身份验证了
FastAPI 中使用 OAuth2 的简单栗子
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} if __name__ == '__main__':
uvicorn.run(app="49_bearer:app", reload=True, host="127.0.0.1", port=8080)
代码解析
- OAuth2 旨在使后端或 API 可以独立于对用户进行身份验证的服务器
- 但在这种情况下,同一个 FastAPI 应用程序将同时处理 API 和身份验证
- 前端请求 /items 的之前要先进行身份验证,也就是用户名和密码,这个验证的路径就是 tokenUrl,是相对路径,POST请求
- oauth2_scheme 中接收一个 str 类型的 token,就是当验证通过后,要返回给客户端的一个令牌(常说的 token)
- 方便下次请求携带这个 token 就可以通过身份认证,这个 token 有过期时间,过期后需要重新验证
OAuth2PasswordBearer
- 使用 OAuth2、密码授权模式、Bearer Token(不记名 token),就是通过 OAuth2PasswordBearer 来完成
- OAuth2PasswordBearer 是接收 URL 作为参数的一个类
- 客户端会向该 URL 发送 username 和 password 参数(通过表单的格式发送),然后得到一个 token 值
- OAuth2PasswordBearer 并不会创建相应的 URL 路径操作,只是指明了客户端用来获取 token 的目标 URL
tokenUrl 是相对路径
- 如果 API 位于 https://example.com/,那么它将引用 https://example.com/token
- 如果API 位于 https://example.com/api/v1/,那么它将引用 https://example.com/api/v1/token
oauth2_scheme
该变量是 OAuth2PasswordBearer 的一个实例,但它也是一个可调用对象,所以它可以用于依赖项
async def read_items(token: str = Depends(oauth2_scheme)):
OAuth2PasswordBearer 会做什么
- 客户端发送请求的时候,FastAPI 会检查请求的 Authorization 头信息,如果没有找到 Authorization 头信息
- 或者头信息的内容不是 Bearer token,它会返回 401 状态码( UNAUTHORIZED )

传递 token 的请求结果


目前因为没有对 token 做验证,所以 token 传什么值都可以验证通过
看看 OAuth2PasswordBearer 的源码

查看 Swagger API 文档

多了个 Authorize 按钮,点击它

可以看到一个包含用户名、密码还有其他可选字段的授权表单
上述代码的问题
还没有获取 token 的路径操作
完善 OAuth2
#!usr/bin/env python
# -*- coding:utf-8 _*-
"""
# author: 小菠萝测试笔记
# blog: https://www.cnblogs.com/poloyy/
# time: 2021/10/6 12:05 下午
# file: 49_bearer.py
"""
from typing import Optional import uvicorn
from fastapi import FastAPI, Depends, 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() oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") # 模拟 hash 加密算法
def fake_hase_password(password: str) -> str:
return "fakehashed" + password # 返回给客户端的 User Model,不需要包含密码
class User(BaseModel):
username: str
email: Optional[str] = None
full_name: Optional[str] = None
disabled: Optional[bool] = None # 继承 User,用于密码验证,所以要包含密码
class UserInDB(User):
hashed_password: str # OAuth2 获取 token 的请求路径
@app.post("/token")
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
# 1、获取客户端传过来的用户名、密码
username = form_data.username
password = form_data.password
# 2、模拟从数据库中根据用户名查找对应的用户
user_dict = fake_users_db.get(username)
if not user_dict:
# 3、若没有找到用户则返回错误码
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="用户名或密码不正确") # 4、找到用户
user = UserInDB(**user_dict)
# 5、将传进来的密码模拟 hash 加密
hashed_password = fake_hase_password(password)
# 6、如果 hash 后的密码和数据库中存储的密码不相等,则返回错误码
if not hashed_password == user.hashed_password:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="用户名或密码不正确") # 7、用户名、密码验证通过后,返回一个 JSON
return {"access_token": user.username, "token_type": "bearer"} # 模拟从数据库中根据用户名查找用户
def get_user(db, username: str):
if username in db:
user_dict = db[username]
return UserInDB(**user_dict) # 模拟验证 token,验证通过则返回对应的用户信息
def fake_decode_token(token):
user = get_user(fake_users_db, token)
return user # 根据当前用户的 token 获取用户,token 已失效则返回错误码
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(user: User = Depends(get_current_user)):
if user.disabled:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid User")
return user # 获取当前用户信息
@app.get("/user/me")
async def read_user(user: User = Depends(get_current_active_user)):
return user # 正常的请求
@app.get("/items/")
async def read_items(token: str = Depends(oauth2_scheme)):
return {"token": token} if __name__ == '__main__':
uvicorn.run(app="49_bearer:app", reload=True, host="127.0.0.1", port=8080)
/token 路径操作函数的响应
# 7、用户名、密码验证通过后,返回一个 JSON
return {"access_token": user.username, "token_type": "bearer"}
- 获取 token 的接口的响应必须是一个 JSON 对象(返回一个 dict 即可)
- 它应该有一个 token_type,当使用 Bearer toklen 时,令牌类型应该是 bearer
- 它应该有一个 access_token,一个包含访问 token 的字符串
- 对于上面简单的例子,返回的 token 是用户名,这是不安全,只是作为栗子好理解一点
返回 401 的HTTPException
# 根据当前用户的 token 获取用户,token 已失效则返回错误码
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
- 任何 HTTP(错误)状态码为 401 UNAUTHORIZED 都应该返回 WWW-Authenticate 的 Header
- 在此处返回的带有值 Bearer 的 WWW-Authenticate Header 也是 OAuth2 规范的一部分
- 在 Beaer token 的情况下,该值应该是 Bearer
- 当然,这并不是必须的,但建议符合规范
查看 Swagger API Authorize


验证通过
请求 /user/me 的结果

请求头带上了 'Authorization: Bearer johndoe'
logout 后再次请求,查看结果

logout 之后,请求头没有 'Authorization: Bearer johndoe' 所以验证就失败啦
验证一个不活跃的用户
authenticate 表单填入
- username:alice
- password:secret2
请求 /users/me
得到的响应
{
"detail": "Inactive user"
}
存在的问题
目前的 token 和验证方式并不安全,下一篇中将介绍 JWT token
FastAPI(58)- 使用 OAuth2PasswordBearer 的简单栗子的更多相关文章
- FastAPI(59)- 详解使用 OAuth2PasswordBearer + JWT 认证
JWT JSON Web Tokens 它是一个将 JSON 对象编码为密集且没有空格的长字符串的标准 使用 JWT token 和安全密码 hash 使应用程序真正安全 JWT 小栗子 eyJhbG ...
- FastAPI 学习之路(二十八)使用密码和 Bearer 的简单 OAuth2
OAuth2 规定在使用(我们打算用的)「password 流程」时,客户端/用户必须将 username 和 password 字段作为表单数据发送.我们看下在我们应该去如何实现呢. 我们写一个登录 ...
- Binder机制-简单用法(一)
Binder算是android里面比较难懂的部分了,但是非常重要,基本上,当我们深入到进程交互的阶段,Binder都是一个绕不开的槛,所以我也希望帮助大家更浅显地了解到这个知识点.笔者想通过3篇博文简 ...
- FastAPI 学习之路(二十七)安全校验
你写API接口肯定你是希望是有权限的人才能访问,没有权限的人是不能访问的,那么我们应该如何去处理呢,我们可以用的验证方式有很多,我们这次分享的是用:OAuth2来认证.那么我们看下,需要怎么才能实现呢 ...
- FastAPI快速查阅
官方文档主要侧重点是循序渐进地学习FastAPI, 不利于有其他框架使用经验的人快速查阅 故本文与官方文档不一样, 并补充了一些官方文档没有的内容 安装 包括安装uvicorn $pip instal ...
- 【Android UI设计与开发】4.底部菜单栏(一)Fragment介绍和简单实现
TabActivity在Android4.0以后已经被完全弃用,取而代之的是Fragment.Fragment是Android3.0新增的概念,Fragment翻译成中文是碎片的意思,不过却和Acti ...
- if最简单的用法
/* Name:if最简单的用法-1 Copyright:By.不懂网络 Author: Yangbin Date:2014年2月9日 03:00:58 Description:if最简单的用法,真则 ...
- java模式:模板模式的简单理解
1.模板模式就是用虚类作为基类将几个要执行差不多操作中相同的部分提取出来,不同的部分各自实现! 2.下面给出简单栗子: 我要进行的操作是将大象和狐狸放入冰箱,放入大象和狐狸有相同的步骤:开冰箱和关冰箱 ...
- python - 常用模块栗子
前言 内容摘自Python参考手册(第4版) 及 自己测试后得出.仅作为一个简单记录,方便使用查询之用. 另 dir(func)及func.__doc__可以查看对象属性及说明文档. 序列Seque ...
随机推荐
- SpringMVC之@ControllerAdvice
@ControllerAdvice ,很多初学者可能都没有听说过这个注解,实际上,这是一个非常有用的注解,顾名思义,这是一个增强的 Controller.使用这个 Controller ,可以实现三个 ...
- Linux命令:ps -ef |grep java
一.ps -ef |grep java 查看包含"java"的所有进程 二.涉及命令详解 ps命令将某个进程显示出来(是LINUX下最常用的也是非常强大的进程查看命令) grep命 ...
- JavaWeb之HttpSession
时间:2016-11-17 22:33 --HttpSession一.HttpSession概述 1.HttpSession是由JavaWeb提供的,用来进行会话跟踪的类. 2.sessi ...
- 再过五分钟,你就懂 HTTP 2.0 了!
Hey guys ,各位小伙伴们大家好,这里是程序员 cxuan,欢迎你收看我最新一期的文章. 这篇文章我们来聊一聊 HTTP 2.0,以及 HTTP 2.0 它在 HTTP 1.1 的基础上做了哪些 ...
- Vue初体验(一)
每个 Vue 应用都需要通过实例化 Vue 来实现. 语法格式如下: var vm = new Vue({ // 选项 }) 接下来让我们通过实例来看下 Vue 构造器中需要哪些内容: 可以看到在 V ...
- rasa form的中断形式 自然机器语言学习 人工智能
Forms形式 最常见的对话模式之一是从用户那里收集一些信息以便做某事(预订餐厅.调用 API.搜索数据库等).这也称为**槽填充**. 用法# 要在 Rasa Open Source 中使用表单,您 ...
- Swift-Button 的 highlighted(高亮)
摘要 在学习小程序时,看到小程序中的一个样式属性 hover-class,通过设置这个属性,就可以给点击的控件添加一个高亮效果.所以也就萌生了在 Swift 也实现一个类似的功能的想法,开干. 下面代 ...
- Jenkins拉取Git远程仓库中指定目录至本地指定目录
Jenkins拉取源码是非常实用的操作,比如每天在跑自动化测试前,拉取Git远程仓库中最新的脚本至本地.那么,Jenkins如何拉取Git远程仓库中指定目录至本地指定目录呢?下面来看看具体的设置方法. ...
- git02
Git Gui的使用 Ssh key 介绍及使用 Ssh key介绍 我理解的就是每台电脑上会产生出一个ssh key,然后自己有一个远程账户,但是自己有可能有很多台电脑, 包括家里的电脑还有公司的电 ...
- 《Go语言圣经》阅读笔记:第三章基础数据类型
第三章 基础数据类型 Go语言将数据类型分为四类: 基础类型 数字 整数 浮点数 复数 字符串 布尔 复合类型 数据 结构体 引用类型 指针 切片 字典 函数 通道 接口类型 在此章节中先介绍基础类型 ...