소개
수정한 내용이 이후 변경에서 실수로 되돌아갈 수 있습니다. 보안 회귀 테스트는 중요한 요구 사항을 반복 가능한 요청과 예상 결과로 바꿉니다.
보호된 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 테스트 세 개와 연결했습니다. 올바른 구현은 통과하고, 준비된 각 우회는 해당 거부 단언에서 탐지됩니다. 이 비교는 테스트가 단순히 오류 없이 실행되는 것을 넘어 동작을 보호한다는 증거를 제공합니다.



