LabHub
배우기 러닝패스 코스

FastAPI — Types Are the Contract

A valid token does not grant document ownership

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

목표

인증·권한·소유권을 분리하고 같은 404로 리소스 존재를 숨깁니다.

왜 중요한가

로그인한 사용자가 주소의 문서 번호만 바꾸어 다른 팀의 문서를 읽었다. 토큰이 유효하다는 사실과 특정 문서를 읽을 권리는 별개다. 이 실습에서는 고정 토큰 사전을 사용해 인증 서버를 대신한다. 토큰을 발급하거나 JWT 서명을 구현하는 실습이 아니라, 인증 결과 이후의 권한 경계를 구현하는 실습이다.

단계

  1. /root/work/fa-ownership-lab/service.py에서 bearer(header)는 정확히 'Bearer '로 시작하고 뒤에 공백 없는 토큰 하나가 있을 때 토큰을 반환합니다. None·빈 토큰·다른 scheme·추가 공백은 ValueError입니다.

처음 한 번 준비하세요. 기존 파일은 덮어쓰지 않습니다.

mkdir -p /root/work/fa-ownership-lab
test -e /root/work/fa-ownership-lab/service.py || cp /opt/fixtures/ten_labs/fa-ownership-lab/service.py /root/work/fa-ownership-lab/service.py
cd /root/work/fa-ownership-lab
  1. /root/work/fa-ownership-lab/service.py에서 principal(token, users)는 토큰 사전의 사용자 {id, scopes}를 반환하되 scopes 리스트까지 복사합니다. 모르는 토큰은 ValueError입니다.

  2. /root/work/fa-ownership-lab/service.py에서 require_scope(user, scope)는 scopes에 scope 문자열이 정확히 있을 때 None, 없으면 PermissionError를 냅니다. read-all은 read가 아닙니다.

  3. /root/work/fa-ownership-lab/service.py에서 visible(user, document)는 document가 None이 아니고 owner가 user의 id와 정확히 같을 때만 True입니다.

  4. /root/work/fa-ownership-lab/service.py에서 public_document(document)는 id와 title만 가진 새 딕셔너리입니다. owner나 internal_cost는 포함하지 않습니다.

  5. /root/work/fa-ownership-lab/service.py에서 authenticate(header, users)는 bearer와 principal을 연결합니다. ValueError는 HTTPException(401)이며 headers의 WWW-Authenticate 값은 Bearer입니다.

  6. /root/work/fa-ownership-lab/service.py에서 read_document(user, documents, document_id)는 read scope 없으면 HTTPException(403), 없거나 남의 문서면 HTTPException(404), 아니면 public_document 결과입니다.

  7. /root/work/fa-ownership-lab/service.py에서 create_app(users, documents)는 GET /documents/{document_id}에서 Authorization 헤더를 받아 authenticate와 read_document를 호출하는 FastAPI 앱을 반환합니다. 200·401·403·404와 비공개 필드 제거를 실제 요청으로 검증하세요.

참고

Bearer 헤더를 분리한다

/root/work/fa-ownership-lab/service.py에서 bearer(header)는 정확히 'Bearer '로 시작하고 뒤에 공백 없는 토큰 하나가 있을 때 토큰을 반환합니다. None·빈 토큰·다른 scheme·추가 공백은 ValueError입니다.

처음 한 번 준비하세요. 기존 파일은 덮어쓰지 않습니다.

mkdir -p /root/work/fa-ownership-lab
test -e /root/work/fa-ownership-lab/service.py || cp /opt/fixtures/ten_labs/fa-ownership-lab/service.py /root/work/fa-ownership-lab/service.py
cd /root/work/fa-ownership-lab

헤더를 임의로 여러 조각으로 나누면 공백 오류를 정상 토큰으로 받아들일 수 있습니다.

저장 후 bash /opt/lab/checks/fa-ownership-lab/01-contract.sh로 확인하세요.

신원을 복사해 반환한다

/root/work/fa-ownership-lab/service.py에서 principal(token, users)는 토큰 사전의 사용자 {id, scopes}를 반환하되 scopes 리스트까지 복사합니다. 모르는 토큰은 ValueError입니다.

반환된 scopes를 수정해 원래 사용자 권한까지 바뀌면 요청 간 권한이 섞입니다.

저장 후 bash /opt/lab/checks/fa-ownership-lab/02-contract.sh로 확인하세요.

권한은 정확히 비교한다

/root/work/fa-ownership-lab/service.py에서 require_scope(user, scope)는 scopes에 scope 문자열이 정확히 있을 때 None, 없으면 PermissionError를 냅니다. read-all은 read가 아닙니다.

부분 문자열 비교는 더 긴 권한 이름을 다른 권한으로 오해합니다.

저장 후 bash /opt/lab/checks/fa-ownership-lab/03-contract.sh로 확인하세요.

소유권을 별도로 확인한다

/root/work/fa-ownership-lab/service.py에서 visible(user, document)는 document가 None이 아니고 owner가 user의 id와 정확히 같을 때만 True입니다.

리소스 없음과 타인 소유를 같은 판정으로 묶습니다.

저장 후 bash /opt/lab/checks/fa-ownership-lab/04-contract.sh로 확인하세요.

응답 필드를 허용 목록으로 고른다

/root/work/fa-ownership-lab/service.py에서 public_document(document)는 id와 title만 가진 새 딕셔너리입니다. owner나 internal_cost는 포함하지 않습니다.

원본에서 필드를 지우지 말고 새 응답을 조립합니다.

저장 후 bash /opt/lab/checks/fa-ownership-lab/05-contract.sh로 확인하세요.

오류를 HTTP 계약에 맞춘다

/root/work/fa-ownership-lab/service.py에서 authenticate(header, users)는 bearer와 principal을 연결합니다. ValueError는 HTTPException(401)이며 headers의 WWW-Authenticate 값은 Bearer입니다.

인증 실패와 애플리케이션 오류를 500 하나로 뭉치지 않습니다.

저장 후 bash /opt/lab/checks/fa-ownership-lab/06-contract.sh로 확인하세요.

거절 순서를 고정한다

/root/work/fa-ownership-lab/service.py에서 read_document(user, documents, document_id)는 read scope 없으면 HTTPException(403), 없거나 남의 문서면 HTTPException(404), 아니면 public_document 결과입니다.

인증 이후에도 scope와 소유권은 각각 확인해야 합니다.

저장 후 bash /opt/lab/checks/fa-ownership-lab/07-contract.sh로 확인하세요.

실제 요청에서 경계를 닫는다

/root/work/fa-ownership-lab/service.py에서 create_app(users, documents)는 GET /documents/{document_id}에서 Authorization 헤더를 받아 authenticate와 read_document를 호출하는 FastAPI 앱을 반환합니다. 200·401·403·404와 비공개 필드 제거를 실제 요청으로 검증하세요.

함수가 따로 맞아도 경로에서 호출을 빠뜨리면 접근 제어는 적용되지 않습니다.

저장 후 bash /opt/lab/checks/fa-ownership-lab/08-contract.sh로 확인하세요.