Browse Source
Co-authored-by: Sebastián Ramírez <[email protected]> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>pull/11377/head
committed by
GitHub
1 changed files with 107 additions and 0 deletions
@ -0,0 +1,107 @@ |
|||||
|
# HTTP 基础授权 |
||||
|
|
||||
|
最简单的用例是使用 HTTP 基础授权(HTTP Basic Auth)。 |
||||
|
|
||||
|
在 HTTP 基础授权中,应用需要请求头包含用户名与密码。 |
||||
|
|
||||
|
如果没有接收到 HTTP 基础授权,就返回 HTTP 401 `"Unauthorized"` 错误。 |
||||
|
|
||||
|
并返回含 `Basic` 值的请求头 `WWW-Authenticate`以及可选的 `realm` 参数。 |
||||
|
|
||||
|
HTTP 基础授权让浏览器显示内置的用户名与密码提示。 |
||||
|
|
||||
|
输入用户名与密码后,浏览器会把它们自动发送至请求头。 |
||||
|
|
||||
|
## 简单的 HTTP 基础授权 |
||||
|
|
||||
|
* 导入 `HTTPBsic` 与 `HTTPBasicCredentials` |
||||
|
* 使用 `HTTPBsic` 创建**安全概图** |
||||
|
* 在*路径操作*的依赖项中使用 `security` |
||||
|
* 返回类型为 `HTTPBasicCredentials` 的对象: |
||||
|
* 包含发送的 `username` 与 `password` |
||||
|
|
||||
|
```Python hl_lines="2 6 10" |
||||
|
{!../../../docs_src/security/tutorial006.py!} |
||||
|
``` |
||||
|
|
||||
|
第一次打开 URL(或在 API 文档中点击 **Execute** 按钮)时,浏览器要求输入用户名与密码: |
||||
|
|
||||
|
<img src="/img/tutorial/security/image12.png"> |
||||
|
|
||||
|
## 检查用户名 |
||||
|
|
||||
|
以下是更完整的示例。 |
||||
|
|
||||
|
使用依赖项检查用户名与密码是否正确。 |
||||
|
|
||||
|
为此要使用 Python 标准模块 <a href="https://docs.python.org/3/library/secrets.html" class="external-link" target="_blank">`secrets`</a> 检查用户名与密码: |
||||
|
|
||||
|
```Python hl_lines="1 11-13" |
||||
|
{!../../../docs_src/security/tutorial007.py!} |
||||
|
``` |
||||
|
|
||||
|
这段代码确保 `credentials.username` 是 `"stanleyjobson"`,且 `credentials.password` 是`"swordfish"`。与以下代码类似: |
||||
|
|
||||
|
```Python |
||||
|
if not (credentials.username == "stanleyjobson") or not (credentials.password == "swordfish"): |
||||
|
# Return some error |
||||
|
... |
||||
|
``` |
||||
|
|
||||
|
但使用 `secrets.compare_digest()`,可以防御**时差攻击**,更加安全。 |
||||
|
|
||||
|
### 时差攻击 |
||||
|
|
||||
|
什么是**时差攻击**? |
||||
|
|
||||
|
假设攻击者试图猜出用户名与密码。 |
||||
|
|
||||
|
他们发送用户名为 `johndoe`,密码为 `love123` 的请求。 |
||||
|
|
||||
|
然后,Python 代码执行如下操作: |
||||
|
|
||||
|
```Python |
||||
|
if "johndoe" == "stanleyjobson" and "love123" == "swordfish": |
||||
|
... |
||||
|
``` |
||||
|
|
||||
|
但就在 Python 比较完 `johndoe` 的第一个字母 `j` 与 `stanleyjobson` 的 `s` 时,Python 就已经知道这两个字符串不相同了,它会这么想,**没必要浪费更多时间执行剩余字母的对比计算了**。应用立刻就会返回**错误的用户或密码**。 |
||||
|
|
||||
|
但接下来,攻击者继续尝试 `stanleyjobsox` 和 密码 `love123`。 |
||||
|
|
||||
|
应用代码会执行类似下面的操作: |
||||
|
|
||||
|
```Python |
||||
|
if "stanleyjobsox" == "stanleyjobson" and "love123" == "swordfish": |
||||
|
... |
||||
|
``` |
||||
|
|
||||
|
此时,Python 要对比 `stanleyjobsox` 与 `stanleyjobson` 中的 `stanleyjobso`,才能知道这两个字符串不一样。因此会多花费几微秒来返回**错误的用户或密码**。 |
||||
|
|
||||
|
#### 反应时间对攻击者的帮助 |
||||
|
|
||||
|
通过服务器花费了更多微秒才发送**错误的用户或密码**响应,攻击者会知道猜对了一些内容,起码开头字母是正确的。 |
||||
|
|
||||
|
然后,他们就可以放弃 `johndoe`,再用类似 `stanleyjobsox` 的内容进行尝试。 |
||||
|
|
||||
|
#### **专业**攻击 |
||||
|
|
||||
|
当然,攻击者不用手动操作,而是编写每秒能执行成千上万次测试的攻击程序,每次都会找到更多正确字符。 |
||||
|
|
||||
|
但是,在您的应用的**帮助**下,攻击者利用时间差,就能在几分钟或几小时内,以这种方式猜出正确的用户名和密码。 |
||||
|
|
||||
|
#### 使用 `secrets.compare_digest()` 修补 |
||||
|
|
||||
|
在此,代码中使用了 `secrets.compare_digest()`。 |
||||
|
|
||||
|
简单的说,它使用相同的时间对比 `stanleyjobsox` 和 `stanleyjobson`,还有 `johndoe` 和 `stanleyjobson`。对比密码时也一样。 |
||||
|
|
||||
|
在代码中使用 `secrets.compare_digest()` ,就可以安全地防御全面攻击了。 |
||||
|
|
||||
|
### 返回错误 |
||||
|
|
||||
|
检测到凭证不正确后,返回 `HTTPException` 及状态码 401(与无凭证时返回的内容一样),并添加请求头 `WWW-Authenticate`,让浏览器再次显示登录提示: |
||||
|
|
||||
|
```Python hl_lines="15-19" |
||||
|
{!../../../docs_src/security/tutorial007.py!} |
||||
|
``` |
Loading…
Reference in new issue