将安全要求转化为测试

PythonBeginner
立即练习

介绍

后续变更可能意外撤销先前的修复。安全回归测试将重要要求转化为可重复执行的请求和预期结果。

你将扩展一个用于受保护 DeskLite 端点的小型 Python 模板,然后使用两个故意存在缺陷的本地服务来检查测试本身。无需安装测试框架,也无需测试编写经验。

测试受保护端点

在本步骤中,你将识别测试必须保护的行为。

预置的正确服务位于本地端口 8080。它的 /tickets 端点在令牌有效时返回原始工单,在令牌缺失或错误时返回 401 和错误对象。身份认证和业务响应是独立要求:仅有状态 200 不能证明返回了正确工单。

cd /home/labex/project/ticket-tests

本实验中可见的 course_test_fixture_token 是公开且有意虚构的测试数据。它仅被这些隔离教学服务接受,在外部没有任何访问能力。它不能代替前面学习的私密应用凭据。

使用与令牌实验相同的 curl 请求头选项 -H:

curl -i -H 'Authorization: Bearer course_test_fixture_token' http://127.0.0.1:8080/tickets

预期返回 HTTP 200 和 {"tickets": [{"id": 1, "title": "Printer offline"}]}。-i 包含 HTTP 响应头,其中包括状态信息。

现在完全省略请求头:

curl -i http://127.0.0.1:8080/tickets

预期返回 HTTP 401 和 {"error": "unauthorized"}。该拒绝是必需行为,不是练习发生故障。

解读小型测试模板

在本步骤中,你将把测试中的每一行与真实请求的一部分联系起来。

cat test_security.py

预置文件包含:

from api import request

def test_valid_token():
    response = request("/tickets", {"Authorization": "Bearer course_test_fixture_token"})
    assert response["status"] == 200
    assert response["body"] == {"tickets": [{"id": 1, "title": "Printer offline"}]}

from api import request 加载预置请求辅助函数。def test_valid_token(): 定义有名称的测试函数。缩进行属于该函数,与前一个实验中的查询函数一样。

request(path, headers) 发送真实 HTTP 请求,并返回 Python 字典:名称到值的映射。请求头映射也使用花括号。{"Authorization": "Bearer ..."} 发送一个请求头,而 {} 不发送任何请求头。response["status"] 读取状态项;response["body"] 读取解析后的 JSON 响应体。

断言表达一个预期。Python 的 == 比较两个值,与用于赋值的单个 = 不同。条件不成立的 assert 会引发 AssertionError,使测试失败。最后一行比较完整的预期业务对象,包括工单标题。

辅助函数根据运行器的目标参数选择预置服务。你将编辑测试,而非辅助函数或服务。

发现通过的测试中的覆盖缺口

在本步骤中,你将了解为什么仅有通过的正向测试还不够。

运行预置测试运行器。参数 good 选择正确服务:

python3 run_tests.py good

预期 test_valid_token 显示 "result": "PASS"。运行器收集名称以 test_ 开头的函数,并逐一调用。全部通过时退出状态为 0,断言失败时为 1,测试或运行时错误时为 2。

另外两个服务故意存在缺陷:missing-auth 接受没有令牌的请求;wrong-auth 接受错误令牌。它们的正向响应仍然正常。分别尝试:

python3 run_tests.py missing-auth
python3 run_tests.py wrong-auth

二者仍通过唯一现有测试。这暴露了覆盖缺口。测试从未发送应被拒绝的请求,因此无法发现任何一种绕过。

添加缺失令牌的拒绝要求

在本步骤中,你将添加一个测试,预期真实的未认证请求被拒绝。

打开测试文件:

nano test_security.py

保留正向测试,并在其下添加新函数。完整文件应为:

from api import request

def test_valid_token():
    response = request("/tickets", {"Authorization": "Bearer course_test_fixture_token"})
    assert response["status"] == 200
    assert response["body"] == {"tickets": [{"id": 1, "title": "Printer offline"}]}

def test_missing_token():
    response = request("/tickets", {})
    assert response["status"] == 401
    assert response["body"] == {"error": "unauthorized"}

空映射 {} 表示没有请求头。状态断言检查拒绝行为,响应体断言确保错误响应不含工单数据。保留四空格缩进。使用 Ctrl+O、Enter 保存,然后使用 Ctrl+X 退出。

python3 run_tests.py good

两个测试都应通过。现在使用故意存在缺失请求头鉴权缺陷的服务:

python3 run_tests.py missing-auth

预期 test_missing_token 显示 "result": "FAIL" 和 "error": "AssertionError"。这次有意的退出状态 1 提供有用证据:新测试检测到服务接受了本应拒绝的请求。它与导入错误或服务器连接错误不同。

添加错误令牌的拒绝要求

在本步骤中,你将覆盖凭据存在但无效的情况。

请求头存在不代表它有效。仅测试缺失请求头,会漏掉接受任意非空令牌的服务。打开同一文件并添加第三个函数,保留前两个:

nano test_security.py

使用以下完整文件:

from api import request

def test_valid_token():
    response = request("/tickets", {"Authorization": "Bearer course_test_fixture_token"})
    assert response["status"] == 200
    assert response["body"] == {"tickets": [{"id": 1, "title": "Printer offline"}]}

def test_missing_token():
    response = request("/tickets", {})
    assert response["status"] == 401
    assert response["body"] == {"error": "unauthorized"}

def test_wrong_token():
    response = request("/tickets", {"Authorization": "Bearer wrong_fixture_token"})
    assert response["status"] == 401
    assert response["body"] == {"error": "unauthorized"}

wrong_fixture_token 被有意设为不被正确服务接受。使用 Ctrl+O、Enter、Ctrl+X 保存并退出。

python3 run_tests.py good

预期三个测试全部通过。然后测试错误请求头鉴权回归:

python3 run_tests.py wrong-auth

预期只有 test_wrong_token 以 AssertionError 失败。不要为了让这个缺陷服务通过,而将 401 改成 200;那会移除安全要求。

验证两种回归检测

在本步骤中,你将针对全部三个实现检查完整测试集。

python3 run_tests.py good
python3 run_tests.py missing-auth
python3 run_tests.py wrong-auth

阅读各项结果。正确服务必须通过全部三个测试。缺失请求头鉴权回归必须仅使 test_missing_token 失败,错误请求头鉴权回归必须仅使 test_wrong_token 失败。有效令牌的业务测试必须在所有实现上持续通过。

这些失败的对照运行是有意安排的。语法错误、连接错误或无条件失败不能证明检测有效。课程检查也会确认测试向每个原始服务发送了三种真实 HTTP 请求。

这些对照覆盖两种已知回归,不能证明所有可能的身份认证或授权性质。

以正确服务的全通过运行结束

在本步骤中,你将以面向预期实现、可重复执行的测试命令结束练习。

cat test_security.py

确认原始正向测试和两个拒绝测试都仍存在。其名称描述了三个独立要求。

python3 run_tests.py good

三个结果都应为 PASS。未来的交付检查关卡可以对预期服务运行同一命令。故意存在缺陷的服务仍保留为教学对照;它们不是要交付的实现。你已经保护了真实行为,而非提交关于测试覆盖率的书面声明。

总结

你将明确的身份认证要求与三个真实 HTTP 测试联系起来。正确实现通过测试;每种预置绕过都会被对应的拒绝断言检测到。这个对照提供了证据,说明测试保护了行为,而非仅仅执行时没有错误。