자료마다 권한 검사하기
65분 안팎
학습 목표
서버에서 자료 소유자와 역할을 확인합니다.
개념
실제 자료를 기준으로 검사합니다
접근표가 정확해도 라우터가 그 함수를 호출하지 않으면 사용자 자료가 새어 나갑니다. 이번 레슨은 이전 자료 생성·조회 코드에 사용자 신원을 연결하고 수정·삭제 경로까지 추가합니다. 서버는 먼저 세션을 조회하고 자료를 저장소에서 가져온 다음 canAccess로 주체·자료·동작을 비교합니다. 클라이언트가 보낸 소유자와 비교하면 요청자가 owner를 자기 이름으로 바꿀 수 있으므로 저장된 item.owner를 사용합니다.
자료를 만드는 순간 소유자 확정
POST /documents는 title과 content를 검사한 뒤 owner를 세션의 user.id로 정합니다. 요청 본문의 owner와 role은 신원 출처가 아닙니다. demo-alice가 body.owner를 demo-bob으로 보내더라도 새 자료 소유자는 demo-alice여야 합니다. 나중에 소유자 변경 기능이 필요하다면 별도 권한과 감사 계약을 설계합니다. 현재 PATCH는 title과 content만 바꾸며 id와 owner를 유지합니다. 객체 전체를 합쳐 저장하는 코드는 이 제한을 깨기 쉽습니다.
조회 경로의 순서
GET /documents/1은 로그인 세션 확인, 경로의 숫자 형식 확인, 자료 찾기, 권한 검사, 응답 복사 순으로 진행합니다. 세션이 없으면 401이고 로그인했지만 자료가 없거나 권한이 없으면 404입니다. 이 앱은 외부에 자료 존재를 구별하지 않으려 타인 거절과 자료 없음 응답을 맞춥니다. 403을 쓰는 다른 앱의 정책이 틀렸다는 뜻은 아닙니다. 상태 정책은 업무 요구에 맞게 정하고 테스트와 문서를 같은 기준으로 유지합니다.
읽기와 변경의 차이
관리자가 타인 자료를 읽을 수 있다고 수정·삭제까지 허용하지 않습니다. canAccess는 GET·PATCH·DELETE 목록에 포함되는지 먼저 확인한 뒤 소유자 또는 관리자 GET 조건을 계산합니다. PUT이라는 새 메서드를 추가해도 기존 조건에서 자동 허용되지 않습니다. 알 수 없는 역할은 같은 owner라도 거절합니다. OWASP의 요청별 권한 검사 원칙을 각 라우트에 적용합니다.
변경 전에 권한을 판정
PATCH는 권한 검사 후 title과 content를 검증하고 자료를 바꿉니다. DELETE도 허용 결과를 얻은 뒤 Map에서 삭제합니다. 변경하고 나서 권한을 검사해 404를 돌려주면 상태 코드만 정상이고 데이터는 이미 훼손된 것입니다. 따라서 거절 시험은 응답뿐 아니라 이후 소유자 조회의 내용까지 비교합니다. 삭제 거절 뒤 자료가 계속 존재하는지, 수정 거절 뒤 원래 title이 그대로인지가 중요한 후조건입니다.
식별자를 비밀로 취급하지 않습니다
자료 id가 순서대로 증가해도 서버 권한 검사가 정확하면 타인의 접근은 거절되어야 합니다. 임의 UUID를 쓰면 추측을 어렵게 할 수 있지만 소유자 검사 자체를 대신하지 않습니다. 화면에서 자기 자료 링크만 보이게 해도 HTTP 경로를 직접 지정하는 요청이 존재합니다. 이번 시험은 화면 조작 대신 함수에 경로를 직접 전달해 서버 규칙을 확인합니다. 허가되지 않은 외부 앱에 같은 요청을 보내는 실습은 하지 않습니다.
응답 객체 복사
GET과 생성 응답은 자료 객체의 얕은 복사본을 반환합니다. 이 교재의 필드는 숫자와 문자열뿐이므로 응답의 title을 바꿔도 Map의 값이 바뀌지 않습니다. 중첩 객체가 추가되면 얕은 복사만으로 충분한지 다시 판단해야 합니다. 권한 검사와 응답 분리는 서로 다른 요구입니다. 테스트는 생성 응답 title을 바꾼 뒤 다시 조회해 원래 값인지 확인합니다. 함수 호출 테스트에서 저장소 참조가 노출되는 우회를 줄입니다.
빈 입력과 없는 자료
/documents/01은 정규식 계약에서 거절하고 /documents/999는 저장소 조회에서 없는 자료로 처리합니다. 같은 404여도 원인은 다르므로 시험 이름을 구분합니다. 잘못된 생성 입력에는 400을 반환하고 nextId를 늘리지 않습니다. 실패한 생성을 여러 번 시도한 뒤 첫 정상 자료가 id 1인지 비교하면 검증 전에 상태를 바꾸지 않았는지 확인할 수 있습니다. 인증이 없는 요청은 입력이 올바르더라도 생성할 수 없습니다.
starter에서 의도된 누락 찾기
starter의 canAccess는 정상 역할·메서드만 확인하고 return true로 끝납니다. 로그인한 bob의 타인 읽기·수정·삭제 시험이 expected 404, actual 200으로 실패합니다. 세션 코드가 틀린 것으로 보고 로그인 자체를 막으면 소유자 작업까지 깨집니다. user.id와 item.owner의 관계 및 관리자 GET 조건으로 마지막 반환을 완성합니다. 공통 검사 함수를 만드는 것과 모든 자료 경로에서 그 함수를 호출하는 것을 함께 리뷰합니다.
위조 요청을 직접 구성
생성 요청에 owner: demo-bob과 role: admin을 넣고 실제 owner가 demo-alice인지 검사합니다. 수정 요청에 같은 필드를 넣어도 소유자가 바뀌지 않는지 봅니다. 쿠키를 사용자 이름으로 채우는 요청은 저장된 세션을 찾지 못해 401이어야 합니다. 이 세 시험은 역할 문자열, 소유자 필드, 토큰 형식이라는 서로 다른 신뢰 경계를 보여 줍니다. 한 가지 위조가 거절되었다고 다른 입력의 처리도 안전하다고 추정하지 않습니다.
로그의 진단 정보 제한
로그에는 서버가 만든 requestId, 메서드, 경로, 상태를 남기고 쿠키·비밀번호·자료 본문은 넣지 않습니다. 권한 거절을 추적하려고 전체 요청 객체를 직렬화하면 민감한 값이 섞일 수 있습니다. E04 증거에도 동작과 거절 상태, 소유자 재조회 상태만 적습니다. 로그가 간결해도 자료 id의 노출 범위나 보관 기간은 별도로 검토할 수 있습니다. 이번 검사는 정해 둔 필드가 원문 비밀을 포함하지 않는지에 한정합니다.
테스트 결과를 작업 설명으로 바꾸기
완료 후 bob이 alice의 자료를 GET·PATCH·DELETE로 요청했을 때 같은 외부 거절을 받았고 자료 내용은 유지되었다고 적습니다. 관리자 GET은 성공하지만 PATCH와 DELETE는 거절되었다는 결과도 포함합니다. 정상 소유자의 조회·변경·삭제가 성공해야 기능을 보존한 수정입니다. 실제 HTTP 헤더 전달은 누적 미션 전체 검사에서 따로 확인합니다. 함수 시험을 브라우저 쿠키나 TLS까지 검사한 결과로 기록하지 않습니다.
따라하기
서버가 결정한 owner
아래 코드를 실행하여 이 단계의 판단 결과를 확인합니다.
const user={id:'demo-alice'};
const body={title:'memo',owner:'demo-bob',role:'admin'};
const stored={title:body.title,owner:user.id};
console.log(stored.owner);실행 결과
demo-alice
거절 전 변경하지 않기
아래 코드를 실행하여 이 단계의 판단 결과를 확인합니다.
const item={title:'before',owner:'demo-alice'};
const allowed='demo-bob'===item.owner;
if(allowed)item.title='after';
console.log('allowed:',allowed);
console.log('title:',item.title);실행 결과
allowed: false title: before
알 수 없는 역할의 처리
아래 코드를 실행하여 이 단계의 판단 결과를 확인합니다.
for(const role of ['member','admin','unknown'])console.log(role,['member','admin'].includes(role));실행 결과
member true admin true unknown false
실습의 실패와 수정 대조
ZIP 루트에서 bash check.sh를 실행합니다. starter의 실패 시험 이름과 expected/actual을 읽고 구현을 수정합니다. solution에서도 같은 명령으로 전체 통과와 종료 코드 0을 확인합니다.
확인 문제
실습
app.cjs의 canAccess를 완성합니다. 소유자는 GET/PATCH/DELETE, 관리자는 타인 GET만 허용하고 거절된 변경은 상태를 보존합니다. 테스트 파일을 수정하지 않습니다.
실행 명령
bash check.sh
기대 결과
11개 테스트 묶음 통과, 종료 코드 0
모범 답안
모범 답안 내려받기더 읽기
면접 질문
- 로그인한 사용자가 다른 사람의 자료를 읽는 문제를 설명합니다.
- 취약점 수정 전후의 테스트 내용을 설명합니다.