이 문서를 통해 아래 질문들에 답할 수 있게 됩니다.
- API를 구현하기 전에 어떤 보안 결정을 문서화해야 하는가?
- actor, asset, trust boundary, abuse story가 API 계약에 어떻게 반영되는가?
- 설계 전 체크리스트의 완료 증거는 무엇이어야 하는가?
개요
API 보안은 Controller를 만든 뒤 annotation을 붙이는 작업이 아니다. 설계 단계에서 이미 누가 어떤 자산에 접근하는지, 어떤 입력이 신뢰 경계를 넘는지, 실패가 어떤 응답과 로그로 남는지가 정해져야 한다.
설계 전 체크리스트는 구현을 늦추기 위한 문서가 아니다. 오히려 구현 중 “이 userId를 믿어도 되는가”, “404와 403 중 무엇을 반환해야 하는가”, “고객센터 예외는 누가 승인하는가” 같은 논쟁을 줄이는 장치다.
체크리스트는 완료 표시가 아니라 검증 가능한 결정 기록이어야 한다. 테스트 이름, event name, 정책 문서 링크, risk acceptance owner 같은 증거가 있어야 한다.
이 문서의 핵심은 “무엇을 만들 것인가”와 함께 “무엇을 막을 것인가”를 API 계약에 포함하는 것이다.
공격 시나리오
- 공격자는 새 API가 기본 허용 상태로 배포된 순간을 노린다.
- 설계서에 actor와 resource ownership이 없으면
ownerId,memberId,tenantId조작이 구현에서 누락된다. - 실패 응답 기준이 없으면 일부 endpoint는 403, 일부는 200 false, 일부는 stack trace를 반환한다.
- rate limit와 abuse story가 없으면 정상 기능 반복 호출로 비용과 데이터를 소모한다.
- 감사 로그 event가 정해져 있지 않으면 사고 후 신규 API의 행위를 추적하지 못한다.
- 고객센터나 batch 같은 예외 경로가 설계에서 빠지면 운영 중 우회 통로가 된다.
취약한 요청/응답 예시
POST /api/refunds HTTP/1.1
Host: api.example.com
Authorization: Bearer user-token
Content-Type: application/json
{"orderId":9001,"ownerId":2002,"amount":120000,"reason":"manual"}design-review=passed
authz=todo
refund-audit-log=not-defined
abuse-limit=not-needed설계 단계에서 owner, 승인 조건, 금액 제한, 감사 로그, abuse 제한이 비어 있으면 구현자는 각자 추측해 만든다.
주제별 핵심 판단
- API마다 actor, asset, action, resource owner를 명시한다.
- 신뢰 경계를 넘는 모든 입력을 나열한다.
- 인증 필요 여부와 인가 정책을 분리한다.
- 객체 수준 권한과 기능 수준 권한을 함께 본다.
- 입력 검증, 출력 형식, 오류 응답, 감사 로그를 API 계약에 포함한다.
- 민감 행위는 재인증, MFA, 승인, 지연 처리 필요성을 판단한다.
- abuse story와 rate limit key를 설계 단계에서 잡는다.
개선된 요청/응답 예시
api=POST /api/refunds
actor=member
asset=payment/refund
action=request_refund
ownerCheck=order.member_id == principal.member_id
amountLimit=paid_amount - refunded_amount
approval=manual_review_required_over_100000
auditEvent=refund_requested
abuseLimit=member+order+ip, 5/day
requiredTests=non_owner_order, amount_over_paid, duplicate_request, missing_auth설계 체크리스트는 구현자가 확인할 정책과 QA가 확인할 테스트를 같은 문장에 연결해야 한다.
방어 설계
- API spec에 보안 섹션을 둔다.
- 인증, 객체 권한, 기능 권한, rate limit, 로그, 오류 응답을 각각 분리해 적는다.
- resource id는 클라이언트가 보낼 수 있지만 소유권은 서버 데이터로 확인한다고 명시한다.
- request DTO에 허용할 필드와 금지할 필드를 적는다.
- 상태 변경 API는 idempotency, CSRF, replay, 중복 요청 처리 기준을 정한다.
- 민감 이벤트는 사용자 알림과 관리자 감사 로그 필요성을 판단한다.
- 예외 경로, 예를 들어 admin, support, batch, migration API도 같은 기준으로 설계한다.
- 보안 결정을 바꾸면 테스트와 로그 event도 함께 바꾼다.
Spring/HTTP 예시
@PreAuthorize("@refundPolicy.canRequest(authentication, #request.orderId())")
@PostMapping("/api/refunds")
public ResponseEntity<RefundResponse> requestRefund(@Valid @RequestBody RefundRequest request) {
Refund refund = refundService.request(request);
return ResponseEntity.accepted().body(RefundResponse.from(refund));
}public boolean canRequest(Authentication authentication, long orderId) {
MemberId memberId = principalResolver.memberId(authentication);
return orderRepository.existsByIdAndMemberId(orderId, memberId);
}설계 전 체크리스트의 ownerCheck가 구현 코드의 policy와 repository 조건으로 이어져야 한다.
운영 로그와 감사 지점
level=INFO event=api_security_design_accepted result=allowed api=POST /api/refunds reviewer=security:kim evidence=ADR-42 tests=RefundSecurityTest traceId=ab810c2flevel=WARN event=api_security_design_gap result=blocked api=POST /api/refunds reason=missing_non_owner_test owner=team-payment due=2026-07-05 traceId=ab810c2f- 체크리스트 통과는 누가 어떤 증거로 승인했는지 남긴다.
- 빠진 항목은 blocker인지 risk acceptance인지 구분한다.
- risk acceptance에는 owner와 만료일을 둔다.
- 설계 변경이 보안 정책을 바꾸면 ADR이나 PR 링크로 추적한다.
- 신규 public endpoint inventory에 자동 등록되는지 확인한다.
실전 판단 기준
- 설계서에 actor만 있고 resource owner가 없으면 인가 누락이 생긴다.
- 인증 필요 여부만 있고 action 권한이 없으면 기능 수준 권한이 빠진다.
- 오류 응답을 정하지 않으면 정보 노출과 클라이언트 혼란이 생긴다.
- 로그 event 이름이 없으면 구현자는 자유 문장 로그를 남기기 쉽다.
- batch와 admin API를 나중에 생각하면 우회 경로가 된다.
- abuse story가 없으면 정상 기능 남용을 rate limit로 막기 어렵다.
테스트 포인트
- 설계 문서에 actor, asset, action, ownerCheck가 모두 있는지 확인한다.
- API spec에 400, 401, 403, 404, 409, 429 기준이 있는지 확인한다.
- 민감 action의 auditEvent 이름이 정해져 있는지 확인한다.
- 비소유자, 권한 없는 role, 예상 밖 필드 테스트가 requiredTests에 있는지 확인한다.
- support/admin/batch 예외 경로가 설계에 포함되어 있는지 확인한다.
- risk acceptance 항목에 owner와 만료일이 있는지 확인한다.
위험 신호!
- “인증 필요”만 있고 리소스 소유권 설명이 없다.
- request field 목록에
role,ownerId,approved가 있으나 검증 위치가 없다. - 테스트 항목이 성공 케이스뿐이다.
- audit log가 “나중에 추가”로 남아 있다.
- rate limit가 모든 endpoint에 같은 값으로만 적혀 있다.
- risk acceptance가 영구 예외처럼 남아 있다.
확인 질문
확인 질문
- 설계 전 체크리스트의 첫 질문은 무엇인가?
- 누가 어떤 자산에 어떤 action을 하며, 그 권한과 소유권은 어디서 확인되는가다.
- 설계 단계에서 로그를 정해야 하는 이유는 무엇인가?
- 구현 후 자유 문장 로그가 생기면 사고 조사와 탐지가 어렵기 때문이다.
- 완료 증거는 무엇이어야 하는가?
- 테스트 이름, 정책 문서, 코드 위치, event name, reviewer, risk acceptance 기록이다.