Web Security 실전 가이드북
이 가이드북을 쓰는 법
이 문서는 Web Security 문서 묶음을 다시 요약하는 문서가 아니다.
실제 개발 중 애매한 상황을 만났을 때 무엇부터 보고, 어떤 순서로 판단하고, 어느 문서를 다시 열어야 하는지 안내하는 작업용 지도다.
각 Guide는 하나의 실전 상황에서 시작한다. 먼저 증거를 보고, 판단 순서를 잡은 뒤, 관련 문서로 돌아가도록 구성했다.
좋은 사용법은 간단하다.
- 지금 겪는 상황과 가장 가까운 Guide를 고른다.
먼저 판단할 것에서 아직 모르는 값을 표시한다.확인할 증거를 실제 코드, 로그, 요청/응답에서 찾는다.찾아볼 문서를 열어 세부 기준을 확인한다.- 마지막에
가져갈 한 문장을 자신의 체크리스트로 바꾼다.
커버리지 지도
| 기존 문서 | 연결되는 Guide | 다시 봐야 하는 이유 |
|---|---|---|
| Web Security 전체 지도 압축 정리 | Guide 1, 14 | 전체 요청 흐름과 학습 순서를 잡는 출발점이다. |
| 웹 요청과 보안 경계 전체 흐름 | Guide 1, 11, 14 | HTTP 요청이 어떤 경계를 지나며 신뢰 수준이 바뀌는지 확인한다. |
| 공격자 관점으로 보는 웹 서비스 | Guide 1, 12, 14 | 정상 요청에서 공격자가 바꿀 수 있는 값을 찾는 연습에 필요하다. |
| 인증 인가 브라우저 API 보안 연결 | Guide 2, 5, 6, 14 | 인증, 인가, 브라우저 정책, API 보안이 한 요청에서 연결되는 지점을 본다. |
| 개인 프로젝트와 기업 보안 수준 비교 | Guide 13, 14 | 작은 서비스와 운영 서비스에서 보안 기본선을 어떻게 조절할지 판단한다. |
| Web Security 학습 로드맵 | Guide 14 | 학습 순서를 실전 점검 루틴으로 바꾸는 기준이다. |
| Threat Model Trust Boundary 압축 정리 | Guide 1, 11 | 자산, actor, entry point, trust boundary를 한 번에 정리한다. |
| Asset Actor Entry Point | Guide 1, 3, 11 | 보호 자산과 진입점을 놓치면 이후 테스트가 흐려진다. |
| Trust Boundary와 Attack Surface | Guide 1, 6, 11 | 어느 값이 어느 경계에서 검증되어야 하는지 정한다. |
| STRIDE와 웹 백엔드 위협 모델링 | Guide 1, 11 | 추상적인 걱정을 테스트와 로그 항목으로 바꾼다. |
| 보안 요구사항을 설계에 반영하는 법 | Guide 1, 13 | 보안 요구사항을 API 계약, 코드, 테스트로 내려보낸다. |
| 보안 리뷰 질문지 | Guide 12, 14 | 리뷰에서 물어야 할 질문을 구체화한다. |
| Authentication과 Identity 압축 정리 | Guide 2 | 인증 흐름 전체를 빠르게 다시 잡는다. |
| Authentication Identity Principal | Guide 2, 5 | principal과 identity를 혼동하지 않게 한다. |
| Login Flow와 Credential 검증 | Guide 2, 12 | 로그인 실패 응답, credential 검증, 계정 열거 위험을 점검한다. |
| Session 기반 인증 | Guide 2, 4 | session id, cookie, 폐기 정책을 함께 판단한다. |
| MFA와 계정 탈취 방어 | Guide 2, 10 | 탈취 의심 계정의 step-up과 MFA 이벤트를 확인한다. |
| 로그인 실패 Abuse와 운영 로그 | Guide 2, 10 | credential stuffing과 장애를 로그로 구분한다. |
| Session Cookie JWT Token 압축 정리 | Guide 4 | session, cookie, JWT, access/refresh token의 차이를 잡는다. |
| Session과 Cookie 보안 모델 | Guide 2, 4, 6 | cookie 속성과 CSRF/XSS 영향을 함께 본다. |
| JWT 구조와 검증 | Guide 4, 12 | issuer, audience, signature, 만료 검증을 점검한다. |
| Access Token Refresh Token 설계 | Guide 4, 10 | token 탈취 사고에서 폐기 범위를 정한다. |
| Token 저장 위치와 탈취 위험 | Guide 4, 6, 10 | localStorage, cookie, memory 저장의 공격면을 비교한다. |
| Logout Revocation Rotation | Guide 4, 10 | logout과 사고 대응용 revocation을 구분한다. |
| Authorization RBAC ABAC Permission 압축 정리 | Guide 3 | 권한 모델 전체 구조를 빠르게 다시 잡는다. |
| Authentication과 Authorization 분리 | Guide 3, 5, 12 | 로그인 성공과 resource 접근 허용을 분리한다. |
| RBAC Role Permission | Guide 3, 12 | role과 permission, meta-permission을 구분한다. |
| ABAC와 Resource Ownership | Guide 3, 5 | 소유권과 context 기반 권한 검사를 설계한다. |
| Method Security와 API 권한 검사 | Guide 3, 8, 12 | URL rule 밖의 Service 경계를 점검한다. |
| 권한 감사 로그와 우회 위험 | Guide 3, 10, 12 | 권한 변경과 권한 거부가 나중에 복원되는지 확인한다. |
| Browser Security 압축 정리 | Guide 6 | SOP, CORS, CSRF, XSS, SameSite, CSP의 역할을 한 번에 본다. |
| Same-Origin Policy와 브라우저 보안 모델 | Guide 6 | 브라우저가 막는 것과 서버가 직접 막아야 하는 것을 나눈다. |
| CORS Preflight와 실전 디버깅 | Guide 6, 13 | CORS 오류인지 보안 사고인지 구분한다. |
| CSRF 공격 원리와 방어 설계 | Guide 6, 10 | cookie 자동 전송과 상태 변경 요청을 함께 본다. |
| XSS 공격 원리와 출력 저장 DOM 방어 | Guide 6, 10 | 출력 context, token 저장 위치, 사고 대응을 연결한다. |
| SameSite CSP 보안 헤더 | Guide 6, 13 | 보안 header가 실제로 어떤 피해를 줄이는지 확인한다. |
| API Security SQL Injection Validation 압축 정리 | Guide 5 | API 입력 검증과 SQLi, Mass Assignment, 오류, abuse를 묶어 본다. |
| SQL Injection 공격과 Prepared Statement | Guide 5, 12 | 값 binding과 query structure allowlist를 구분한다. |
| Input Validation과 Output Encoding | Guide 5, 6 | 입력 검증과 출력 인코딩을 섞지 않는다. |
| Mass Assignment와 DTO 경계 | Guide 5, 8 | DTO가 endpoint별 allowlist인지 확인한다. |
| Error Message 정보 노출 방지 | Guide 5, 12 | 외부 응답과 내부 로그의 정보 수준을 나눈다. |
| Rate Limit과 Abuse 방어 | Guide 5, 10, 13 | 반복 호출과 업무 자원 소모를 제한한다. |
| OAuth2 OIDC Social Login 압축 정리 | Guide 7 | OAuth2와 OIDC의 경계를 빠르게 다시 잡는다. |
| OAuth2 역할과 Authorization Code Flow | Guide 7 | Authorization Code Flow의 actor와 channel을 확인한다. |
| PKCE State Redirect URI 보안 | Guide 7, 12 | callback 혼동, code 탈취, redirect URI 문제를 점검한다. |
| OIDC ID Token과 UserInfo | Guide 7 | ID Token과 UserInfo를 내부 권한으로 바로 쓰지 않는다. |
| Social Login 계정 연결 위험 | Guide 7, 10 | 소셜 로그인 성공과 계정 연결을 분리한다. |
| Token 교환 저장 갱신 운영 | Guide 7, 10 | provider token 저장, 갱신, 폐기, 로그를 확인한다. |
| Password Hashing Secret Crypto 압축 정리 | Guide 9 | password hashing, secret, crypto, TLS를 한 번에 정리한다. |
| Password Hashing Salt Work Factor | Guide 2, 9 | 비밀번호 저장과 work factor migration을 판단한다. |
| bcrypt Argon2 PBKDF2 선택 기준 | Guide 9 | 운영 가능한 password hashing 알고리즘을 고른다. |
| Secret 환경 변수 Vault Rotation | Guide 9, 10, 13 | secret 저장, 접근, 회전, 유출 대응을 본다. |
| 암호화와 서명 실수 | Guide 9 | 암호화와 서명의 목적을 혼동하지 않는다. |
| TLS 인증서와 키 관리 기본 | Guide 9, 13 | 인증서와 key lifecycle을 운영 기준으로 본다. |
| Security Log Audit Abuse Detection 압축 정리 | Guide 10 | 보안 이벤트와 감사 로그의 전체 구조를 잡는다. |
| Audit Log와 Security Event | Guide 10, 12 | 감사 기록과 탐지 신호를 구분한다. |
| 로그인 권한 변경 결제 이벤트 추적 | Guide 10 | 여러 이벤트의 순서를 복원한다. |
| 이상 요청 Abuse Detection | Guide 10, 13 | 정상 기능 남용 패턴과 대응 action을 설계한다. |
| 개인정보 마스킹과 로그 보존 | Guide 10, 13 | 로그가 증거이면서 유출 지점이 되지 않게 한다. |
| 보안 사고 초기 대응 | Guide 10 | 증거 보존과 containment의 순서를 잡는다. |
| API 보안 체크리스트 압축 정리 | Guide 11, 12, 13 | 설계, 구현, 테스트, 배포, 리뷰 체크를 연결한다. |
| API 설계 전 보안 체크리스트 | Guide 1, 11 | API 계약 전에 보안 질문을 넣는다. |
| 구현 중 보안 체크리스트 | Guide 5, 8, 11 | Controller, Service, Repository 경계를 구현 중에 확인한다. |
| 테스트와 취약점 검증 체크리스트 | Guide 11, 12 | 실패해야 하는 요청과 남아야 하는 로그를 테스트한다. |
| 배포 전 운영 보안 체크리스트 | Guide 13 | production 설정, secret, rollback, monitoring을 확인한다. |
| 코드 리뷰 보안 질문 | Guide 12 | diff를 공격 요청으로 읽는다. |
| Spring Security는 어디까지 책임지는가 압축 정리 | Guide 8 | Spring Security의 책임과 애플리케이션 책임을 나눈다. |
| Security FilterChain의 책임 | Guide 8, 13 | FilterChain이 맡는 첫 방어선을 확인한다. |
| Authentication Provider SecurityContext | Guide 2, 8 | Authentication과 SecurityContext의 범위를 확인한다. |
| Authorization Manager Method Security | Guide 3, 8 | AuthorizationManager와 Method Security의 역할을 본다. |
| Gateway WAF Infra와 책임 경계 | Guide 8, 13 | 앞단 인프라와 애플리케이션 책임을 나눈다. |
| Spring Security로 해결되지 않는 보안 문제 | Guide 8, 11 | 프레임워크 밖에 남는 입력, 데이터, 로그 책임을 확인한다. |
| Web Security 실전 플레이북 압축 정리 | Guide 10, 14 | 사고 대응을 탐지, 증거, containment, 재발 방지로 바꾼다. |
| 로그인 장애와 계정 탈취 의심 대응 | Guide 2, 10 | 로그인 장애와 계정 탈취 시도를 구분한다. |
| Token 탈취와 세션 무효화 대응 | Guide 4, 10 | token family와 기기 세션 기준으로 폐기 범위를 잡는다. |
| 권한 우회 취약점 대응 | Guide 3, 10 | 권한 우회 사고의 영향 범위를 산정한다. |
| CORS CSRF XSS 사고 대응 | Guide 6, 10 | 브라우저 보안 사고의 containment를 나눈다. |
| 보안 사고 보고서와 재발 방지 | Guide 10, 14 | 사고 기록을 재발 방지 항목으로 바꾼다. |
실전 Guide
Guide 1. 새 API를 만들기 전에 보안 경계부터 그린다
상황
팀에서 POST /teams/{teamId}/invitations API를 만들기로 했다.
기능 요구사항은 단순하다. 팀 관리자가 이메일을 입력하면 초대 메일이 발송되고, 받은 사람은 링크로 가입한다.
하지만 이 기능은 초대 token, role, 이메일, 팀 권한, 만료 시간, 감사 로그가 모두 얽힌다. 바로 Controller부터 만들면 나중에 “누가 누구를 어떤 권한으로 초대할 수 있는가”가 흐려진다.
먼저 판단할 것
- 보호 자산은 초대 token인가, 팀 멤버십인가, role 변경 권한인가?
- actor는 팀 관리자, 일반 멤버, 초대받은 사용자, 공격자, 메일 시스템 중 누구인가?
- entry point는 초대 생성 API만인가, 초대 수락 URL과 재발송 API도 포함하는가?
- 요청 body의
role과email은 어느 경계에서 검증되는가? - 실패했을 때 어떤 로그가 남아야 나중에 초대 남용을 복원할 수 있는가?
확인할 증거
- API 설계 문서에 actor, resource, action이 분리되어 있는지 본다.
- 초대 token이 응답, 로그, 메일 템플릿, trace에 원문으로 남는지 본다.
- role 값이 client 요청에서 온 값인지 서버 policy에서 제한되는 값인지 본다.
- 초대 수락 시 이미 가입된 계정과 새 계정의 흐름이 갈라지는지 본다.
- 초대 생성, 수락, 만료, 취소가 감사 로그로 남는지 본다.
진행 순서
- 먼저 asset, actor, entry point를 표로 쓴다.
- 각 entry point에서 신뢰 경계를 표시한다.
- STRIDE 또는 abuse case로 조작 가능한 값을 찾는다.
- 보안 요구사항을 API 계약과 테스트 이름으로 바꾼다.
- 구현 전 체크리스트에 owner, token, role, log 항목을 넣는다.
찾아볼 문서
- 웹 요청과 보안 경계 전체 흐름: 요청이 어디서 신뢰 수준을 바꾸는지 그린다.
- 공격자 관점으로 보는 웹 서비스: 새 API를 공격자가 어떤 값부터 바꿔 볼지 예상한다.
- Threat Model Trust Boundary 압축 정리: 설계 전에 자산, actor, entry point, trust boundary 전체 구조를 빠르게 잡는다.
- Asset Actor Entry Point: 보호 자산과 actor를 먼저 분리한다.
- Trust Boundary와 Attack Surface: 초대 API의 공격면을 넓게 잡는다.
- STRIDE와 웹 백엔드 위협 모델링: role 조작, token 탈취, 부인 가능성을 분류한다.
- 보안 요구사항을 설계에 반영하는 법: 추상 요구사항을 검증 가능한 문장으로 바꾼다.
- API 설계 전 보안 체크리스트: 설계 단계에서 빠진 보안 질문을 채운다.
흔한 실수
- 초대 생성 API만 보고 초대 수락, 재발송, 취소, 만료를 놓친다.
role을 enum validation만 하고 권한 정책으로 검증하지 않는다.- 초대 token을 로그에 남겨 운영 로그가 2차 유출 지점이 된다.
- “관리자만 호출한다”는 말로 팀 소유권과 관리자 범위를 생략한다.
정리
새 API의 보안은 구현 뒤에 붙이는 장식이 아니다. 기능 요구사항이 생기는 순간 actor, asset, entry point, trust boundary를 같이 잡아야 한다.
가져갈 한 문장
API를 만들기 전에는 URL보다 먼저 “누가 어떤 자산에 어떤 권한으로 닿는가”를 적는다.
Guide 2. 로그인 장애가 터졌을 때 공격과 장애를 나눈다
상황
오전 9시 20분부터 로그인 실패율이 급증했다.
고객지원에는 “비밀번호가 맞는데 로그인이 안 된다”는 문의가 들어오고, 보안 알림에는 여러 국가 IP에서 실패 이벤트가 보인다.
바로 계정을 잠그면 정상 사용자 피해가 커질 수 있고, 단순 장애로 보면 credential stuffing을 놓칠 수 있다.
먼저 판단할 것
- 실패가 특정 내부 의존성 장애로 몰리는가, 여러 계정과 출처에 분산되는가?
- 실패 후 성공한 계정이 있는가?
- MFA challenge 반복이나 새 기기 등록이 이어지는가?
- session과 refresh token을 함께 무효화해야 하는 계정이 있는가?
- 사용자 공지 전에 확정된 사실과 추정이 분리되어 있는가?
확인할 증거
auth.login.failed,auth.login.succeeded,mfa.challenge,password.changed로그를 시간순으로 본다.- 계정별, IP별, ASN별, user agent별 실패 분포를 본다.
- 동일 credential pair 반복인지 password spraying인지 구분한다.
- 인증 서버, DB, 외부 OAuth provider, SMS/Mail provider 장애 지표를 확인한다.
- 보호 조치 후 기존 session과 token이 실제로 거부되는지 본다.
진행 순서
- 장애 가능성과 공격 가능성을 동시에 열어 둔다.
- 내부 의존성 장애 지표를 먼저 확인한다.
- 실패 이벤트를 계정, 출처, credential 패턴으로 묶는다.
- 실패 후 성공과 민감 변경 이벤트를 연결한다.
- 위험 계정만 우선 보호하고 전체 잠금은 마지막 선택지로 남긴다.
- 사용자가 해야 할 조치와 아직 확인 중인 사실을 분리해 공지한다.
찾아볼 문서
- Authentication과 Identity 압축 정리: 인증 경계 전체를 다시 잡는다.
- Authentication Identity Principal: 로그인 장애 분석에서 identifier, credential, principal을 분리한다.
- Login Flow와 Credential 검증: 실패 응답과 credential 검증 기준을 본다.
- Session 기반 인증: 계정 보호 조치가 기존 session에 어떻게 반영되는지 확인한다.
- MFA와 계정 탈취 방어: MFA 피로 공격과 step-up 기준을 확인한다.
- 로그인 실패 Abuse와 운영 로그: 실패 이벤트를 공격 신호로 해석한다.
- Session과 Cookie 보안 모델: session 폐기와 cookie 동작을 확인한다.
- Password Hashing Salt Work Factor: 비밀번호 저장 자체가 안전한지 배경을 본다.
- Authentication Provider SecurityContext: 인증 이벤트와 SecurityContext 범위를 확인한다.
- 로그인 장애와 계정 탈취 의심 대응: 실제 대응 순서를 잡는다.
흔한 실수
- 실패 횟수만 보고 모든 계정을 잠근다.
- 장애와 공격 로그를 같은 이벤트 이름으로 남긴다.
- 비밀번호 재설정만 요구하고 기존 session과 refresh token을 방치한다.
- MFA가 있으니 탈취 위험이 낮다고 단정한다.
정리
로그인 장애 대응은 인증 기능만 보는 일이 아니다. credential, MFA, session, token, 사용자 공지, 고객지원까지 하나의 흐름으로 판단해야 한다.
가져갈 한 문장
로그인 실패가 늘면 먼저 “장애인가, 공격인가, 둘 다인가”를 증거로 나눈다.
Guide 3. 권한 우회가 의심되면 패치보다 영향 범위를 먼저 잡는다
상황
사용자 A가 /api/orders/9001을 호출했는데 사용자 B의 주문이 보였다는 제보가 들어왔다.
개발자는 @PreAuthorize를 추가하면 된다고 말한다.
하지만 이미 누가 어떤 주문을 읽었고, 수정 API도 같은 문제가 있는지 모르면 패치만으로 끝낼 수 없다.
먼저 판단할 것
- 이 문제는 인증 실패인가, 인가 실패인가?
- URL rule, Method Security, Service policy, Repository 조건 중 어디가 비어 있는가?
- 목록, 상세, export, batch, 관리자 대리 조회가 같은 권한 조건을 쓰는가?
- 영향 범위는 endpoint 기준인가, resource type과 action 기준인가?
- 거부 로그와 허용 로그로 이미 노출된 데이터를 복원할 수 있는가?
확인할 증거
- 정상 사용자와 비소유자 사용자의 요청/응답을 비교한다.
actor,resource,action,owner,result,reason로그가 있는지 본다.- Repository가
findById만 쓰는지findByIdAndOwnerId같은 조건을 쓰는지 본다. - 관리자, 고객지원, batch, 내부 API가 같은 Service policy를 통과하는지 본다.
- 사고 기간 동안 200 응답 중 owner mismatch가 있었는지 역추적한다.
진행 순서
- 인증 성공과 권한 허용을 분리한다.
- 영향 resource와 action 목록을 먼저 산정한다.
- 임시 containment로 위험 endpoint나 export를 제한한다.
- Service policy와 Repository 조건을 함께 수정한다.
- 비소유자 테스트를 CI에 넣는다.
- 사고 보고서에 노출 범위와 남은 불확실성을 남긴다.
찾아볼 문서
- Authorization 압축 정리: 권한 모델 전체를 다시 잡는다.
- Authentication과 Authorization 분리: 인증과 인가를 분리한다.
- RBAC Role Permission: role과 permission, meta-permission이 우회 범위에 어떤 영향을 주는지 본다.
- ABAC와 Resource Ownership: 소유권과 context 조건을 점검한다.
- Method Security와 API 권한 검사: URL 밖 Service 경계를 본다.
- 권한 감사 로그와 우회 위험: 권한 실패와 변경 이벤트를 남긴다.
- Authorization Manager Method Security: Spring Security 인가 기능의 역할을 확인한다.
- 권한 우회 취약점 대응: 사고 대응 순서를 잡는다.
흔한 실수
isAuthenticated()를 권한 검사로 착각한다.- Controller annotation만 추가하고 Repository 조건을 그대로 둔다.
- 관리자 예외 경로를 “운영자만 쓰니까 안전하다”고 본다.
- 영향 범위 산정 없이 “수정 완료”라고 보고한다.
정리
권한 우회 대응의 중심은 코드 한 줄이 아니라 actor, resource, action, owner, log를 같은 정책으로 묶는 일이다.
가져갈 한 문장
권한 우회는 패치 전에 “누가 무엇을 봤는가”부터 복원한다.
Guide 4. JWT와 refresh token을 쓰는 서비스의 로그아웃을 설계한다
상황
서비스가 JWT 기반 인증으로 전환했다.
access token은 15분, refresh token은 14일이다. 사용자는 “전체 기기 로그아웃” 기능을 요구하고, 보안팀은 refresh token 재사용 탐지를 원한다.
단순히 브라우저 저장소를 비우는 것으로는 충분하지 않다.
먼저 판단할 것
- access token과 refresh token의 저장 위치는 어디인가?
- refresh token은 rotation되는가, token family가 있는가?
- logout은 현재 기기만 종료하는가, 전체 기기를 종료하는가?
- 비밀번호 변경, MFA 변경, 탈취 의심 시 token 폐기 범위가 다른가?
- resource server가 폐기된 token을 실제로 거부하는가?
확인할 증거
- JWT의 issuer, audience, exp, nbf, alg, key id 검증 코드를 본다.
- refresh token 저장소에 원문이 아니라 hash와 family id가 있는지 본다.
- token rotation 후 이전 token 재사용이 사고 이벤트로 남는지 본다.
- logout API가 서버 저장소를 갱신하는지 본다.
- access token 폐기 지연과 cache TTL을 확인한다.
진행 순서
- session 기반인지 token 기반인지 먼저 분리한다.
- access token과 refresh token의 책임을 나눈다.
- refresh token family와 device session 모델을 설계한다.
- logout, revocation, rotation, password change 이벤트를 연결한다.
- 사고 대응 시 token 폐기와 사용자 재로그인 UX를 함께 준비한다.
찾아볼 문서
- Session Cookie JWT Token 압축 정리: session, cookie, JWT, token lifecycle을 비교한다.
- JWT 구조와 검증: JWT 검증 필드를 확인한다.
- Access Token Refresh Token 설계: access/refresh 책임을 나눈다.
- Token 저장 위치와 탈취 위험: 저장 위치별 공격면을 본다.
- Logout Revocation Rotation: logout과 revocation을 구분한다.
- Token 탈취와 세션 무효화 대응: 탈취 사고 대응 범위를 잡는다.
흔한 실수
- JWT는 stateless라서 logout이 필요 없다고 말한다.
- refresh token을 원문으로 DB와 로그에 남긴다.
- 현재 기기 로그아웃과 전체 기기 로그아웃을 같은 동작으로 구현한다.
- token 폐기 후 resource server에서 실제 거부되는지 확인하지 않는다.
정리
token 기반 인증에서는 발급보다 폐기와 재사용 탐지가 더 어렵다. 처음부터 token family, device session, revocation event를 설계해야 한다.
가져갈 한 문장
JWT 전환의 핵심 질문은 “어떻게 발급할까”보다 “어떻게 끊을까”다.
Guide 5. 요청 DTO를 만들 때 보안 경계를 함께 만든다
상황
프로필 수정 API를 만들고 있다.
프론트 화면에는 nickname만 있지만, 응답 JSON에는 role, point, emailVerified도 보인다.
개발자는 빠르게 구현하려고 entity를 request body로 받으려 한다.
먼저 판단할 것
- 이 endpoint에서 사용자가 바꿀 수 있는 필드는 정확히 무엇인가?
- request DTO가 allowlist 역할을 하는가?
- unknown field는 무시할 것인가, 거부할 것인가?
- Service가 업무 의미를 가진 domain method를 호출하는가?
- 민감 필드 변경은 별도 관리자 endpoint와 감사 로그를 가지는가?
확인할 증거
- Controller가 entity를 직접 받는지 본다.
- DTO에 사용자가 바꿀 수 없는 필드가 들어 있는지 본다.
- mapper가 명시적으로 필드를 복사하는지 본다.
- Repository 저장 전 권한과 소유권 검사가 있는지 본다.
- 실패 응답과 로그가 민감값을 노출하지 않는지 본다.
진행 순서
- endpoint별 허용 필드 목록을 만든다.
- request DTO를 허용 필드만 담는 allowlist로 만든다.
- Bean Validation으로 형식을 검증한다.
- Service에서 소유권, 상태 전이, 업무 의미를 검증한다.
- Repository에서 데이터 범위를 좁힌다.
- 화면에 없는 필드를 넣는 테스트를 추가한다.
찾아볼 문서
- API Security 압축 정리: 입력, 오류, abuse 방어를 묶어 본다.
- Input Validation과 Output Encoding: 입력 검증과 출력 인코딩을 나눈다.
- Mass Assignment와 DTO 경계: DTO가 보안 경계가 되는 이유를 확인한다.
- Error Message 정보 노출 방지: 실패 응답과 내부 로그를 분리한다.
- Rate Limit과 Abuse 방어: 입력 검증을 통과한 정상 요청이 반복 남용될 때의 제한 기준을 본다.
- 구현 중 보안 체크리스트: Controller, Service, Repository 경계를 점검한다.
- Spring Security로 해결되지 않는 보안 문제: 프레임워크 밖의 DTO 책임을 확인한다.
흔한 실수
- DTO를 쓰면서도 entity의 모든 필드를 그대로 복사한다.
@Valid가 권한 필드 오염을 막아 준다고 착각한다.- blocklist로 민감 필드를 막으려다 새 필드가 추가될 때 빠뜨린다.
- 사용자용 endpoint와 관리자용 endpoint가 같은 DTO를 공유한다.
정리
request DTO는 단순한 데이터 운반 객체가 아니라 사용자가 바꿀 수 있는 필드의 계약이다.
가져갈 한 문장
DTO는 편의 객체가 아니라 endpoint별 입력 allowlist다.
Guide 6. CORS 오류가 났을 때 설정부터 열지 않는다
상황
프론트 개발자가 “CORS 때문에 API가 안 된다”고 말한다.
급한 마음에 allowedOrigins("*")를 넣고 싶은데, 해당 API는 사용자 결제 정보와 프로필을 반환한다.
이 문제가 단순 개발 환경 오류인지, credential 포함 CORS 위험인지 먼저 구분해야 한다.
먼저 판단할 것
- 요청 Origin은 무엇이고, credential이 포함되는가?
- preflight가 실패하는가, 실제 요청이 실패하는가?
- 응답을 외부 Origin의 스크립트가 읽을 수 있는가?
- SameSite cookie, CSRF token, Authorization header 중 어떤 인증 방식을 쓰는가?
- XSS와 token 저장 위치가 이 CORS 판단에 영향을 주는가?
확인할 증거
- 브라우저 Network 탭에서 Origin, method, request headers를 확인한다.
- preflight OPTIONS 응답과 실제 API 응답을 분리해 본다.
Access-Control-Allow-Origin,Allow-Credentials,Vary: Origin을 확인한다.- Spring Security CORS 설정과 gateway CORS 설정이 중복되지 않는지 본다.
- CSRF 실패인지 CORS 실패인지 서버 로그 event를 확인한다.
진행 순서
- 브라우저가 막은 것인지 서버가 거부한 것인지 구분한다.
- credential 포함 여부를 확인한다.
- 허용 Origin을 정확한 scheme, host, port로 제한한다.
- 상태 변경 요청은 CSRF 방어와 함께 본다.
- XSS 가능성과 token 저장 위치를 같이 판단한다.
- 운영 배포 전 CORS와 보안 header를 다시 검증한다.
찾아볼 문서
- 인증 인가 브라우저 API 보안 연결: CORS, CSRF, token, session 판단이 한 요청에서 어떻게 연결되는지 확인한다.
- Browser Security 압축 정리: 브라우저 보안 전체 지도를 확인한다.
- Same-Origin Policy와 브라우저 보안 모델: 브라우저가 막는 것과 서버 책임을 나눈다.
- CORS Preflight와 실전 디버깅: preflight와 실제 요청을 분리한다.
- CSRF 공격 원리와 방어 설계: cookie 기반 상태 변경 위험을 본다.
- XSS 공격 원리와 출력 저장 DOM 방어: token 읽기와 script 실행 위험을 본다.
- SameSite CSP 보안 헤더: cookie와 CSP 방어선을 확인한다.
- CORS CSRF XSS 사고 대응: 사고 상황의 containment를 잡는다.
흔한 실수
- CORS를 서버 간 접근 제어로 이해한다.
- preflight 성공을 인증/인가 성공으로 착각한다.
- credential 포함 API에 동적 Origin 반사를 넣는다.
- CSRF 실패와 CORS 실패를 같은 403으로만 남긴다.
정리
CORS 문제는 개발 편의 설정이 아니라 브라우저가 민감 응답을 어떤 Origin의 스크립트에 노출할지 정하는 문제다.
가져갈 한 문장
CORS를 열기 전에 “누가 응답을 읽게 되는가”를 먼저 묻는다.
Guide 7. 소셜 로그인을 붙일 때 로그인과 계정 연결을 분리한다
상황
서비스에 Google, GitHub 로그인을 붙이기로 했다.
기획은 “이메일이 같으면 기존 계정으로 로그인시키자”고 한다.
그런데 OAuth2 로그인 성공과 내부 계정 연결은 같은 일이 아니다.
먼저 판단할 것
- OAuth2 인증 결과는 어떤 provider와 subject에서 왔는가?
- OIDC ID Token의 issuer, audience, nonce, exp가 검증되는가?
- email은 verified 상태인가, 내부 계정 연결 근거로 충분한가?
- 기존 로그인 사용자가 provider 계정을 연결할 때 재인증이 필요한가?
- provider access token과 내부 session을 섞고 있지 않은가?
확인할 증거
- callback의
state,code,redirect_uri검증을 본다. - PKCE code verifier가 token 교환 시점에 쓰이는지 본다.
(provider, subject)unique constraint가 있는지 본다.- 계정 연결, 해제, 실패, 중복 연결 시도가 감사 로그로 남는지 본다.
- provider token이 원문으로 로그나 DB에 남는지 본다.
진행 순서
- OAuth2와 OIDC의 목적을 나눈다.
- 로그인 flow와 계정 연결 flow를 별도 intent로 둔다.
state, PKCE, redirect URI를 저장 상태와 묶어 검증한다.- ID Token과 UserInfo claim을 내부 권한으로 바로 쓰지 않는다.
- provider token 저장과 갱신, 폐기 정책을 정한다.
- 연결/해제 이벤트를 사용자 알림과 감사 로그로 남긴다.
찾아볼 문서
- OAuth2 OIDC Social Login 압축 정리: OAuth2와 OIDC를 구분한다.
- OAuth2 역할과 Authorization Code Flow: front channel과 back channel을 나눈다.
- PKCE State Redirect URI 보안: callback 혼동을 막는다.
- OIDC ID Token과 UserInfo: ID Token과 UserInfo 검증을 확인한다.
- Social Login 계정 연결 위험: email 자동 연결 위험을 본다.
- Token 교환 저장 갱신 운영: provider token 운영을 확인한다.
흔한 실수
- email만 보고 내부 계정에 자동 연결한다.
- 소셜 로그인 성공을 내부 권한 부여로 바로 해석한다.
- 계정 연결에 최근 재인증을 요구하지 않는다.
- provider access token을 내부 API access token처럼 사용한다.
정리
OAuth2/OIDC는 외부 identity를 확인하는 흐름이고, 내부 계정 연결은 별도의 보안 민감 작업이다.
가져갈 한 문장
소셜 로그인에서 가장 위험한 순간은 로그인 성공이 아니라 내부 계정에 연결하는 순간이다.
Guide 8. Spring Security를 넣었는데도 남는 책임을 표로 만든다
상황
프로젝트에 Spring Security 설정이 들어갔다.
SecurityFilterChain, JWT filter, @PreAuthorize, CORS 설정도 있다.
그런데 프로필 수정 API는 entity를 그대로 받고, 검색 API는 문자열 query를 만들며, 로그에는 token 일부가 남는다.
먼저 판단할 것
- Spring Security가 맡는 책임은 무엇인가?
- 애플리케이션 Service가 직접 맡아야 하는 업무 권한은 무엇인가?
- Repository가 데이터 범위를 좁히는가?
- Gateway/WAF가 막는 것과 못 막는 것은 무엇인가?
- 로그, secret, abuse detection은 어느 팀과 코드가 책임지는가?
확인할 증거
SecurityFilterChainmatcher 순서와 public endpoint를 본다.- AuthenticationProvider가 만든 principal에 어떤 값이 들어가는지 본다.
- Method Security가 self-invocation이나 batch 경로에서도 적용되는지 본다.
- DTO, Repository, audit log가 Spring Security 설정과 같은 정책을 말하는지 본다.
- Gateway/WAF 로그와 애플리케이션 로그가 trace id로 연결되는지 본다.
진행 순서
- Spring Security가 맡는 인증, 공통 인가, CSRF/CORS/header 책임을 적는다.
- 애플리케이션이 맡는 resource ownership과 업무 상태 전이를 적는다.
- Repository가 맡는 tenant/member 조건을 적는다.
- 운영이 맡는 secret, log, monitoring, WAF 책임을 적는다.
- endpoint별로 책임 표를 만든다.
- 책임이 비어 있는 칸을 테스트와 이슈로 바꾼다.
찾아볼 문서
- Spring Security는 어디까지 책임지는가 압축 정리: 전체 책임 경계를 잡는다.
- Security FilterChain의 책임: 첫 방어선의 범위를 확인한다.
- Authentication Provider SecurityContext: principal과 SecurityContext 범위를 본다.
- Authorization Manager Method Security: method 경계의 인가를 확인한다.
- Gateway WAF Infra와 책임 경계: 인프라와 앱 책임을 나눈다.
- Spring Security로 해결되지 않는 보안 문제: DTO, SQLi, XSS, secret, log 책임을 놓치지 않는다.
흔한 실수
- Spring Security가 켜져 있으니 입력 검증도 해결됐다고 생각한다.
- URL matcher만 보고 Service와 Repository 조건을 보지 않는다.
- WAF 차단을 애플리케이션 권한 검사의 대체재로 본다.
- 보안 책임이 팀 사이에서 떠다니는데 문서화하지 않는다.
정리
Spring Security는 강력한 공통 경계다. 하지만 업무 소유권, 입력 allowlist, 데이터 범위, 운영 로그는 애플리케이션이 명시적으로 책임져야 한다.
가져갈 한 문장
Spring Security를 넣은 뒤에는 “무엇을 맡겼고 무엇이 남았는가”를 표로 남긴다.
Guide 9. 비밀번호, secret, key를 같은 방식으로 다루지 않는다
상황
서비스 보안 점검에서 세 가지 질문이 동시에 나왔다.
비밀번호 hash 알고리즘은 무엇인가, JWT signing key는 어디에 있는가, 외부 API key는 언제 회전되는가.
셋 다 “비밀값”처럼 보이지만 처리 방식은 다르다.
먼저 판단할 것
- 비밀번호는 복호화할 값인가, 검증용 verifier인가?
- salt, work factor, migration 전략이 있는가?
- secret은 코드, image, log, CI output에 남아 있지 않은가?
- key는 암호화용인가, 서명용인가?
- TLS 인증서와 private key는 자동 갱신과 폐기 확인이 되는가?
확인할 증거
- password hash에 알고리즘 id와 work factor가 들어 있는지 본다.
- legacy hash를 로그인 시점에 재해시하는지 본다.
- secret inventory와 rotation history가 있는지 본다.
- secret read와 rotation event가 감사 로그로 남는지 본다.
- TLS 인증서 만료, SAN, private key 접근 권한을 확인한다.
진행 순서
- password hashing, secret storage, encryption, signature, TLS를 분리한다.
- password verifier는 Argon2, bcrypt, PBKDF2 같은 password hashing 정책으로 다룬다.
- secret은 저장 위치보다 접근, 감사, rotation, 폐기를 기준으로 본다.
- 암호화와 서명은 얻고 싶은 보안 속성을 먼저 정한다.
- TLS와 key lifecycle은 운영 자동화와 만료 알림까지 포함한다.
찾아볼 문서
- Password Hashing Secret Crypto 압축 정리: password, secret, crypto, TLS를 나눈다.
- Password Hashing Salt Work Factor: 오프라인 공격 방어를 본다.
- bcrypt Argon2 PBKDF2 선택 기준: 운영 가능한 알고리즘을 고른다.
- Secret 환경 변수 Vault Rotation: secret lifecycle을 확인한다.
- 암호화와 서명 실수: 암호화와 서명을 구분한다.
- TLS 인증서와 키 관리 기본: 인증서와 key 운영을 본다.
흔한 실수
- 비밀번호를 암호화해서 저장하려 한다.
- secret을 환경 변수에 넣었다는 이유로 lifecycle 관리가 끝났다고 본다.
- JWT signing key와 encryption key를 같은 rotation 기준으로 둔다.
- TLS 인증서 만료 알림만 있고 갱신 실패 대응이 없다.
정리
비밀번호, secret, key는 모두 민감하지만 같은 문제가 아니다. 얻고 싶은 보안 속성에 따라 저장, 검증, 회전, 폐기 방식이 달라진다.
가져갈 한 문장
비밀값을 다룰 때는 “숨겼는가”보다 “어떻게 검증하고 어떻게 바꿀 수 있는가”를 묻는다.
Guide 10. 사고 대응은 로그를 읽는 일이 아니라 순서를 복원하는 일이다
상황
새벽에 세 가지 알림이 이어졌다.
refresh token 재사용, 특정 계정의 결제 시도 증가, 관리자 권한 변경 실패가 같은 시간대에 발생했다.
각 로그를 따로 보면 작은 이상 징후지만, 순서를 연결하면 계정 탈취와 권한 우회 시도일 수 있다.
먼저 판단할 것
- 같은 actor, account, device, trace id, incident id로 묶이는 이벤트가 있는가?
- 감사 로그와 security event가 같은 목적의 로그로 섞여 있지 않은가?
- 민감값 없이도 영향 범위를 복원할 수 있는가?
- containment 전에 증거 snapshot을 만들었는가?
- 사고 보고서에 남은 불확실성이 기록되는가?
확인할 증거
- login, token, permission, payment, support action 로그를 시간순으로 정렬한다.
- actor와 subject가 분리되어 있는지 본다.
- token 원문, password, 주민번호, 카드번호가 로그에 없는지 본다.
- rate limit과 abuse detection rule이 어떤 key와 window에서 매칭되었는지 본다.
- 사고 대응 조치가 incident id로 남는지 본다.
진행 순서
- 원본 로그와 snapshot을 보존한다.
- 이벤트를 시간순으로 묶는다.
- 허용 이벤트와 거부 이벤트를 모두 본다.
- 영향 계정, token, resource, action 범위를 산정한다.
- containment를 적용하고 그 조치도 감사 로그로 남긴다.
- 보고서에 원인, 영향, 조치, 남은 불확실성, 재발 방지 owner를 남긴다.
찾아볼 문서
- Security Log Audit Abuse Detection 압축 정리: 로그와 탐지 전체 구조를 본다.
- Audit Log와 Security Event: 사실 복원용 기록과 탐지 신호를 나눈다.
- 로그인 권한 변경 결제 이벤트 추적: 여러 이벤트의 순서를 연결한다.
- 이상 요청 Abuse Detection: 정상 기능 남용을 탐지한다.
- 개인정보 마스킹과 로그 보존: 로그가 2차 유출 지점이 되지 않게 한다.
- 보안 사고 초기 대응: 첫 조치 순서를 잡는다.
- Web Security 실전 플레이북 압축 정리: 사고 대응을 운영 행동으로 바꾼다.
- 보안 사고 보고서와 재발 방지: 보고서와 재발 방지 항목을 만든다.
흔한 실수
- 성공 로그만 보고 거부 이벤트를 무시한다.
- 로그에 민감값을 많이 남기면 사고 분석이 쉬워진다고 생각한다.
- 증거 보존 전에 서버 재시작과 로그 삭제를 한다.
- 사고 보고서를 “수정 완료” 한 줄로 끝낸다.
정리
보안 로그의 목적은 나중에 믿고 검색할 수 있는 증거를 남기는 것이다. 사고 대응은 그 증거로 순서를 복원하는 작업이다.
가져갈 한 문장
사고 대응에서 가장 먼저 지켜야 할 것은 서비스만이 아니라 증거다.
Guide 11. 구현 전에 체크리스트를 문서가 아니라 작업 순서로 쓴다
상황
신규 주문 취소 API를 구현해야 한다.
설계 문서도 있고, 보안 체크리스트도 있다.
하지만 체크리스트가 마지막 PR 템플릿에만 붙어 있으면 이미 잘못된 구조가 굳어진 뒤에야 문제를 발견한다.
먼저 판단할 것
- 설계 단계에서 actor, resource, action, owner check가 정의되었는가?
- 구현 중 Controller, Service, Repository 경계가 각각 살아 있는가?
- 테스트는 실패해야 하는 요청을 포함하는가?
- 배포 전 운영 설정과 rollback 경로까지 확인하는가?
- 리뷰 질문이 실제 diff에 붙어 있는가?
확인할 증거
- API 설계서에 보안 요구사항이 명시되어 있는지 본다.
- DTO, policy, repository method, audit event가 같은 action 이름을 쓰는지 본다.
- 테스트에 인증 없음, 권한 없음, 비소유자, 예상 밖 필드, 반복 호출이 있는지 본다.
- 배포 설정에서 CORS, secret, TLS, logging, alert, rollback을 본다.
- 리뷰 코멘트가 재사용 가능한 테스트나 체크리스트 항목으로 남는지 본다.
진행 순서
- 설계 전 체크리스트로 빠진 보안 질문을 찾는다.
- 구현 중 체크리스트로 코드 경계를 확인한다.
- 테스트 체크리스트로 실패 요청과 로그를 검증한다.
- 배포 전 체크리스트로 운영 설정을 확인한다.
- 코드 리뷰 질문으로 diff를 공격 요청처럼 읽는다.
찾아볼 문서
- API 보안 체크리스트 압축 정리: 체크리스트 전체 흐름을 잡는다.
- API 설계 전 보안 체크리스트: 설계 전에 보안 질문을 넣는다.
- 구현 중 보안 체크리스트: 코드 경계를 확인한다.
- 테스트와 취약점 검증 체크리스트: 실패해야 하는 요청을 만든다.
- 배포 전 운영 보안 체크리스트: production 설정을 확인한다.
- 코드 리뷰 보안 질문: 리뷰에서 물어야 할 질문을 정리한다.
흔한 실수
- 체크리스트를 PR 마지막에 형식적으로 붙인다.
- 구현 체크와 운영 체크를 분리하지 않는다.
- 테스트가 happy path만 확인한다.
- 리뷰에서 발견한 보안 문제를 재사용 가능한 항목으로 남기지 않는다.
정리
체크리스트는 문서가 아니라 작업 순서다. 설계, 구현, 테스트, 배포, 리뷰 단계에서 각각 다른 질문을 해야 한다.
가져갈 한 문장
좋은 체크리스트는 “확인했습니다”가 아니라 “이 코드와 이 테스트가 증거입니다”로 끝난다.
Guide 12. 코드 리뷰에서는 diff를 공격 요청처럼 읽는다
상황
PR에 검색 API 개선, 관리자 권한 변경 API, OAuth callback 수정이 함께 들어왔다.
리뷰어가 스타일과 성능만 보면 보안 경계 변경을 놓칠 수 있다.
이 PR에서 공격자가 바꿀 수 있는 값과 실패 시 남는 증거를 따라가야 한다.
먼저 판단할 것
- 새로 열린 entry point가 있는가?
- client 입력이 보안 결정에 바로 쓰이는가?
- 권한 검사가 Controller, Service, Repository 어디에 있는가?
- 오류 응답이 내부 정보를 드러내는가?
- 테스트와 로그가 실패 경로를 증명하는가?
확인할 증거
- 변경된 Controller의 path, method, request body를 본다.
- DTO에 민감 필드가 들어왔는지 본다.
- 동적 query에서 값과 구조가 분리되었는지 본다.
@PreAuthorize, policy service, repository condition을 따라간다.- OAuth callback에서 state, PKCE, redirect URI 검증이 유지되는지 본다.
- 실패 응답과 security event 이름을 확인한다.
진행 순서
- diff에서 새 입력면을 찾는다.
- 입력값 중 공격자가 바꿀 수 있는 값을 표시한다.
- 보안 결정이 서버 데이터와 principal 기준으로 내려지는지 본다.
- 실패 응답과 로그를 확인한다.
- 테스트가 우회 요청을 포함하는지 본다.
- 리뷰 코멘트를 체크리스트나 테스트로 남긴다.
찾아볼 문서
- 보안 리뷰 질문지: 좋은 리뷰 질문의 형태를 본다.
- SQL Injection 공격과 Prepared Statement: query 결합 위험을 본다.
- Mass Assignment와 DTO 경계: DTO allowlist를 확인한다.
- PKCE State Redirect URI 보안: callback 검증이 깨지지 않았는지 본다.
- 테스트와 취약점 검증 체크리스트: 실패 요청과 증거를 테스트한다.
- 코드 리뷰 보안 질문: diff를 공격 요청처럼 읽는 기준을 확인한다.
흔한 실수
- 보안 리뷰를 별도 보안팀만 하는 일로 미룬다.
- 권한 annotation만 보고 데이터 조회 조건을 보지 않는다.
- 테스트가 성공 케이스만 있어서 리뷰 코멘트를 증명하지 못한다.
- OAuth callback 수정에서 state 저장소와 redirect URI 검증을 놓친다.
정리
보안 코드 리뷰는 “이 코드가 예쁜가”가 아니라 “공격자가 어떤 값을 바꾸면 어디서 막히는가”를 확인하는 일이다.
가져갈 한 문장
리뷰할 때는 diff를 기능 변경이 아니라 새 공격면으로 읽는다.
Guide 13. 배포 전에는 코드보다 운영 경계를 다시 본다
상황
보안 테스트는 통과했고 PR도 승인됐다.
하지만 production 배포 직전, gateway CORS 설정과 application CORS 설정이 다르고, secret은 CI log에 일부 출력되며, rollback manifest에는 이전 TLS 설정이 남아 있다.
코드가 안전해도 운영 경계가 틀리면 배포 순간 취약점이 된다.
먼저 판단할 것
- production domain에서 HTTPS, HSTS, Secure cookie가 실제로 적용되는가?
- gateway, WAF, Spring Security matcher가 같은 보호 기준을 가지는가?
- secret은 build가 아니라 runtime에 주입되는가?
- alert와 log retention이 사고 대응에 충분한가?
- rollback하면 오래된 insecure 설정이 되살아나지 않는가?
확인할 증거
- 배포 manifest, gateway route, WAF rule, Spring 설정을 비교한다.
- CORS allowlist와 credential 포함 여부를 production domain 기준으로 본다.
- secret scan 결과와 CI/CD log masking 상태를 확인한다.
- TLS 인증서 만료와 key 접근 권한을 확인한다.
- rate limit, abuse detection, alert rule이 실제 endpoint에 연결되는지 본다.
진행 순서
- production에서 실제 적용될 설정을 기준으로 본다.
- gateway와 애플리케이션의 보안 경계를 비교한다.
- secret과 key가 image, log, env dump에 남지 않는지 본다.
- 보안 header와 cookie 속성을 실제 응답으로 확인한다.
- alert와 rollback이 보안 경계를 깨지 않는지 본다.
- 배포 후 smoke test에 인증, 인가, CORS, 로그 확인을 포함한다.
찾아볼 문서
- 개인 프로젝트와 기업 보안 수준 비교: 배포 전 보안 기준을 서비스 규모와 운영 책임에 맞게 조정한다.
- 배포 전 운영 보안 체크리스트: 배포 직전 운영 경계를 확인한다.
- Security FilterChain의 책임: 앱 내부 공통 방어선을 본다.
- Gateway WAF Infra와 책임 경계: 앞단 인프라 책임을 확인한다.
- CORS Preflight와 실전 디버깅: CORS 운영 설정을 확인한다.
- SameSite CSP 보안 헤더: 보안 header를 실제 응답에서 본다.
- Secret 환경 변수 Vault Rotation: secret runtime 주입과 rotation을 확인한다.
- TLS 인증서와 키 관리 기본: 인증서와 key 운영을 확인한다.
- 이상 요청 Abuse Detection: abuse 탐지와 alert를 확인한다.
흔한 실수
- staging 설정이 안전하니 production도 같을 것이라고 가정한다.
- rollback manifest의 오래된 CORS나 key 설정을 확인하지 않는다.
- secret이 CI log에 찍히지 않는지만 보고 image layer를 확인하지 않는다.
- WAF가 있으니 애플리케이션 권한 검사를 느슨하게 둔다.
정리
배포 전 보안은 코드 리뷰의 반복이 아니다. 실제 운영 경계, secret, header, 로그, rollback을 확인하는 별도 단계다.
가져갈 한 문장
보안 배포는 “코드가 맞다”가 아니라 “production에서 그대로 맞다”를 확인하는 일이다.
Guide 14. 학습을 끝낼 때는 문서를 테스트와 runbook으로 바꾼다
상황
Web Security 문서를 모두 읽었다.
읽을 때는 이해한 것 같지만, 실제 PR이나 사고 상황이 오면 어느 문서를 다시 열어야 할지 막힌다.
학습을 실력으로 바꾸려면 문서 제목을 외우는 대신 자신의 작업 루틴으로 바꿔야 한다.
먼저 판단할 것
- 내가 만든 API에 대해 요청 경계 그림을 그릴 수 있는가?
- 인증, 인가, 브라우저 보안, 입력 검증, 로그를 한 요청에서 연결해 설명할 수 있는가?
- 각 문서에서 테스트로 바꿀 수 있는 질문을 뽑았는가?
- 사고 대응 시 어떤 로그를 먼저 볼지 정해 두었는가?
- 내 프로젝트의 보안 기본선과 운영 확장선을 나누었는가?
확인할 증거
- 내 API 설계서에 actor, resource, action, trust boundary가 있는지 본다.
- 테스트 코드에 인증 없음, 권한 없음, 비소유자, 예상 밖 필드, 잘못된 Origin, 반복 호출이 있는지 본다.
- 로그에 actor, resource, action, result, reason, trace id가 있는지 본다.
- 사고 대응 runbook에 증거 보존, containment, 사용자 보호, 보고서 작성 순서가 있는지 본다.
- 학습 노트가 “설정 이름”보다 “막는 공격”을 중심으로 정리되어 있는지 본다.
진행 순서
- 전체 지도 문서를 다시 열어 요청 흐름을 그린다.
- 각 장의 압축 정리에서 핵심 질문을 뽑는다.
- 상세 문서에서 취약 요청과 개선 요청을 하나씩 골라 내 프로젝트에 맞게 변형한다.
- API 보안 체크리스트를 PR 템플릿이 아니라 작업 순서로 바꾼다.
- 실전 플레이북을 내 서비스의 사고 대응 runbook으로 줄인다.
- 다음 PR에서 이 가이드북의 Guide 하나를 실제로 적용해 본다.
찾아볼 문서
- Web Security 전체 지도 압축 정리: 전체 구조를 다시 잡는다.
- Web Security 학습 로드맵: 학습 순서를 실전 루틴으로 바꾼다.
- 보안 리뷰 질문지: 읽은 내용을 리뷰 질문으로 바꾼다.
- API 보안 체크리스트 압축 정리: 설계부터 배포까지의 체크 순서를 만든다.
- Web Security 실전 플레이북 압축 정리: 사고 대응 루틴으로 연결한다.
- 보안 사고 보고서와 재발 방지: 학습 결과를 재발 방지 문서로 남긴다.
흔한 실수
- 문서를 읽은 순서와 실무에서 판단하는 순서를 같다고 생각한다.
- Spring Security 설정 이름은 외웠지만 어떤 공격을 막는지 설명하지 못한다.
- 체크리스트를 실제 테스트와 로그로 바꾸지 않는다.
- 사고 대응 문서를 읽고도 내 서비스의 로그 필드가 충분한지 확인하지 않는다.
정리
학습의 끝은 문서를 덮는 순간이 아니라, 다음 설계와 PR과 사고 대응에서 다시 열 수 있는 루틴을 만드는 순간이다.
가져갈 한 문장
보안 문서는 읽어서 끝나는 것이 아니라 테스트와 로그와 runbook으로 바뀔 때 실력이 된다.