“CSP 헤더가 뭔가요?”, “RBAC과 ACL의 차이는?”, “보안 로그는 어떻게 관리하나요?” — 기본적인 인증/인가를 넘어, 보안 헤더·권한 모델·감사 로깅까지 이해해야 실무 수준의 보안 설계가 가능하다.
1.1 왜 필요한가
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| ┌─────────────────────────────────────────────────────────────────────┐
│ Security Headers의 필요성 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ HTTP 응답 헤더에 보안 정책을 설정하여 │
│ 브라우저 단에서 공격을 사전 차단한다. │
│ │
│ 서버 로직만으로는 막기 어려운 공격: │
│ • XSS (Cross-Site Scripting) → 악성 스크립트 주입 │
│ • Clickjacking → iframe으로 사용자 클릭 유도 │
│ • MIME Sniffing → 파일 타입 위장 실행 │
│ • Man-in-the-Middle → HTTP 도청 │
│ │
│ → 이 공격들은 브라우저가 응답을 어떻게 처리하느냐에 달려있음 │
│ → Security Headers로 브라우저의 동작을 제한 │
│ │
└─────────────────────────────────────────────────────────────────────┘
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| ┌─────────────────────────────────────────────────────────────────────┐
│ 주요 Security Headers │
├──────────────────────────────┬──────────────────────────────────────┤
│ 헤더 │ 역할 │
├──────────────────────────────┼──────────────────────────────────────┤
│ Content-Security-Policy │ 허용된 리소스 출처를 지정 │
│ (CSP) │ → XSS 공격의 핵심 방어 │
├──────────────────────────────┼──────────────────────────────────────┤
│ X-Frame-Options │ iframe 삽입 허용 여부 │
│ │ → Clickjacking 방어 │
├──────────────────────────────┼──────────────────────────────────────┤
│ X-Content-Type-Options │ MIME 타입 스니핑 방지 │
│ │ → nosniff로 타입 위장 차단 │
├──────────────────────────────┼──────────────────────────────────────┤
│ Strict-Transport-Security │ HTTPS 강제 (HSTS) │
│ (HSTS) │ → HTTP 접근 시 자동 HTTPS 전환 │
├──────────────────────────────┼──────────────────────────────────────┤
│ X-XSS-Protection │ 브라우저 내장 XSS 필터 활성화 │
│ │ (최신 브라우저는 CSP로 대체) │
├──────────────────────────────┼──────────────────────────────────────┤
│ Referrer-Policy │ Referer 헤더 전송 범위 제한 │
│ │ → 민감한 URL 정보 유출 방지 │
├──────────────────────────────┼──────────────────────────────────────┤
│ Permissions-Policy │ 브라우저 기능(카메라, 위치 등) │
│ │ 사용 허용 범위 제한 │
└──────────────────────────────┴──────────────────────────────────────┘
|
1.3 CSP (Content-Security-Policy) 상세
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
| ┌─────────────────────────────────────────────────────────────────────┐
│ CSP 동작 원리 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ CSP = 브라우저에게 "이 출처의 리소스만 실행/로드해라"고 지시 │
│ │
│ [CSP 없이] │
│ <script src="https://evil.com/xss.js"> → 실행됨! │
│ │
│ [CSP 적용] │
│ Content-Security-Policy: script-src 'self' https://cdn.example.com │
│ <script src="https://evil.com/xss.js"> → 차단됨! │
│ │
│ 주요 디렉티브: │
│ ┌──────────────┬──────────────────────────────────────────────┐ │
│ │ default-src │ 모든 리소스의 기본 정책 │ │
│ │ script-src │ JavaScript 출처 제한 │ │
│ │ style-src │ CSS 출처 제한 │ │
│ │ img-src │ 이미지 출처 제한 │ │
│ │ connect-src │ AJAX/WebSocket 연결 대상 제한 │ │
│ │ frame-src │ iframe으로 삽입 가능한 출처 제한 │ │
│ └──────────────┴──────────────────────────────────────────────┘ │
│ │
│ 값 예시: │
│ 'self' → 같은 출처만 허용 │
│ 'none' → 모두 차단 │
│ 'unsafe-inline' → 인라인 스크립트 허용 (보안상 비권장) │
│ https://cdn.example.com → 특정 도메인 허용 │
│ │
└─────────────────────────────────────────────────────────────────────┘
|
1.4 Spring Security에서 헤더 설정
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
| @Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.headers(headers -> headers
// X-Frame-Options: DENY (Clickjacking 방어)
.frameOptions(frame -> frame.deny())
// Content-Security-Policy
.contentSecurityPolicy(csp -> csp
.policyDirectives("default-src 'self'; " +
"script-src 'self' https://cdn.example.com; " +
"style-src 'self' 'unsafe-inline'; " +
"img-src 'self' data: https:; " +
"connect-src 'self' https://api.example.com"))
// HSTS (HTTPS 강제)
.httpStrictTransportSecurity(hsts -> hsts
.includeSubDomains(true)
.maxAgeInSeconds(31536000)) // 1년
// X-Content-Type-Options: nosniff
.contentTypeOptions(content -> {})
// Referrer-Policy
.referrerPolicy(referrer -> referrer
.policy(ReferrerPolicyHeaderWriter.ReferrerPolicy
.STRICT_ORIGIN_WHEN_CROSS_ORIGIN))
);
return http.build();
}
}
|
2. CORS (Cross-Origin Resource Sharing) 심화
2.1 CORS 동작 흐름
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
| ┌─────────────────────────────────────────────────────────────────────┐
│ CORS Preflight 요청 흐름 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 브라우저 (https://frontend.com) │
│ │ │
│ │ [1] Preflight 요청 (사전 확인) │
│ │ OPTIONS /api/users │
│ │ Origin: https://frontend.com │
│ │ Access-Control-Request-Method: POST │
│ │ Access-Control-Request-Headers: Content-Type, Authorization │
│ │ │
│ ▼ │
│ 서버 (https://api.example.com) │
│ │ │
│ │ [2] Preflight 응답 │
│ │ Access-Control-Allow-Origin: https://frontend.com │
│ │ Access-Control-Allow-Methods: GET, POST, PUT │
│ │ Access-Control-Allow-Headers: Content-Type, Authorization │
│ │ Access-Control-Max-Age: 3600 ← 1시간 캐시 │
│ │ │
│ ▼ │
│ 브라우저 │
│ │ │
│ │ [3] 실제 요청 (허용 확인됨) │
│ │ POST /api/users │
│ │ Origin: https://frontend.com │
│ │ Content-Type: application/json │
│ │ │
│ ▼ │
│ 서버 → 정상 응답 │
│ │
│ ※ 단순 요청(Simple Request)은 Preflight 없이 바로 전송 │
│ 조건: GET/HEAD/POST + 기본 헤더만 + Content-Type 제한 │
│ │
└─────────────────────────────────────────────────────────────────────┘
|
2.2 Spring에서 CORS 설정
1
2
3
4
5
6
7
8
9
10
11
12
13
| @Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("https://frontend.com") // 허용 출처 (와일드카드 * 비권장)
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowedHeaders("Content-Type", "Authorization")
.allowCredentials(true) // 쿠키 포함 허용
.maxAge(3600); // Preflight 캐시 1시간
}
}
|
주의: allowedOrigins("*")와 allowCredentials(true)는 동시에 사용할 수 없다. 쿠키를 포함하려면 반드시 출처를 명시해야 한다.
3. RBAC vs ACL vs ABAC
3.1 권한 모델 비교
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
| ┌─────────────────────────────────────────────────────────────────────┐
│ 권한 모델 3종 비교 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ [RBAC - Role-Based Access Control] 역할 기반 │
│ │
│ 사용자 → 역할(Role) → 권한(Permission) │
│ │
│ 예: │
│ 김개발 → ADMIN → [사용자관리, 게시글관리, 통계조회] │
│ 이인턴 → VIEWER → [게시글조회] │
│ │
│ 장점: 관리가 간단, 대부분의 서비스에 적합 │
│ 단점: 세밀한 리소스별 제어 어려움 │
│ 사용: Spring Security @PreAuthorize("hasRole('ADMIN')") │
│ │
│ ───────────────────────────────────────────────── │
│ │
│ [ACL - Access Control List] 접근 제어 목록 │
│ │
│ 리소스마다 누가 어떤 작업을 할 수 있는지 목록으로 관리 │
│ │
│ 예: │
│ 게시글 #42 │
│ │ 김개발 → READ, WRITE, DELETE │
│ │ 이인턴 → READ │
│ │ 박매니저 → READ, WRITE │
│ │
│ 장점: 리소스 단위로 세밀한 제어 │
│ 단점: 리소스가 많아지면 관리 복잡 │
│ 사용: 파일 시스템 권한, Google Docs 공유 설정 │
│ │
│ ───────────────────────────────────────────────── │
│ │
│ [ABAC - Attribute-Based Access Control] 속성 기반 │
│ │
│ 사용자/리소스/환경의 속성(Attribute)을 조합해서 판단 │
│ │
│ 예: │
│ IF user.department == "개발팀" │
│ AND resource.classification != "기밀" │
│ AND time.hour BETWEEN 9 AND 18 │
│ THEN ALLOW │
│ │
│ 장점: 가장 유연하고 세밀한 제어 │
│ 단점: 정책 설계/관리가 복잡 │
│ 사용: 대규모 엔터프라이즈, 정부 시스템 │
│ │
└─────────────────────────────────────────────────────────────────────┘
|
3.2 비교표
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| ┌─────────────────────────────────────────────────────────────────────┐
│ RBAC vs ACL vs ABAC 비교 │
├──────────────┬──────────────┬──────────────┬─────────────────────────┤
│ 항목 │ RBAC │ ACL │ ABAC │
├──────────────┼──────────────┼──────────────┼─────────────────────────┤
│ 제어 단위 │ 역할 (Role) │ 리소스별 │ 속성 조합 │
├──────────────┼──────────────┼──────────────┼─────────────────────────┤
│ 관리 복잡도 │ 낮음 │ 중간 │ 높음 │
├──────────────┼──────────────┼──────────────┼─────────────────────────┤
│ 유연성 │ 보통 │ 높음 │ 매우 높음 │
├──────────────┼──────────────┼──────────────┼─────────────────────────┤
│ 적합한 규모 │ 중소규모 │ 중규모 │ 대규모/엔터프라이즈 │
├──────────────┼──────────────┼──────────────┼─────────────────────────┤
│ 대표 사례 │ 웹 서비스 │ 파일 시스템 │ AWS IAM Policy │
│ │ 관리자 패널 │ 문서 공유 │ 정부 시스템 │
├──────────────┼──────────────┼──────────────┼─────────────────────────┤
│ Spring 지원 │ @PreAuthorize│ Spring ACL │ 커스텀 구현 필요 │
│ │ hasRole() │ 모듈 │ │
└──────────────┴──────────────┴──────────────┴─────────────────────────┘
|
3.3 Spring Security RBAC 구현
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
| // 1. 역할 정의
public enum Role {
ADMIN, MANAGER, USER, VIEWER
}
// 2. 컨트롤러에서 역할 기반 접근 제어
@RestController
@RequestMapping("/api/admin")
public class AdminController {
@PreAuthorize("hasRole('ADMIN')")
@GetMapping("/users")
public List<UserDto> getAllUsers() { ... }
@PreAuthorize("hasAnyRole('ADMIN', 'MANAGER')")
@PutMapping("/users/{id}")
public UserDto updateUser(@PathVariable Long id) { ... }
// 메서드 레벨 권한 체크 (SpEL 활용)
@PreAuthorize("hasRole('ADMIN') or #userId == authentication.principal.id")
@GetMapping("/users/{userId}/detail")
public UserDetailDto getUserDetail(@PathVariable Long userId) { ... }
}
// 3. Security 설정에서 URL 기반 제어
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.requestMatchers("/api/manager/**").hasAnyRole("ADMIN", "MANAGER")
.requestMatchers("/api/user/**").authenticated()
.requestMatchers("/api/public/**").permitAll()
);
return http.build();
}
|
4. 보안 이벤트 로깅 (Audit Logging)
4.1 왜 필요한가
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| ┌─────────────────────────────────────────────────────────────────────┐
│ 감사 로깅의 필요성 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 보안 이벤트 로깅 = 누가, 언제, 무엇을, 어떻게 했는지 기록 │
│ │
│ 필요한 이유: │
│ 1. 보안 사고 발생 시 원인 추적 (포렌식) │
│ 2. 이상 행동 탐지 (비정상 로그인 시도 등) │
│ 3. 규정 준수 (개인정보보호법, 정보통신망법) │
│ 4. 감사(Audit) 요구사항 충족 │
│ │
│ 기록해야 할 이벤트: │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ • 로그인 성공/실패 │ │
│ │ • 권한 변경 (역할 부여/회수) │ │
│ │ • 민감 데이터 접근 (개인정보 조회) │ │
│ │ • 관리자 작업 (사용자 삭제, 설정 변경) │ │
│ │ • API 호출 (특히 쓰기 작업) │ │
│ │ • 인가 실패 (권한 없는 접근 시도) │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
|
4.2 감사 로그 설계
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| ┌─────────────────────────────────────────────────────────────────────┐
│ 감사 로그 구조 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 필수 포함 정보: │
│ ┌──────────────┬──────────────────────────────────────────────┐ │
│ │ timestamp │ 이벤트 발생 시각 (UTC) │ │
│ │ userId │ 행위자 식별 (누가) │ │
│ │ action │ 수행한 작업 (무엇을) │ │
│ │ resource │ 대상 리소스 (어디에) │ │
│ │ result │ 성공/실패 │ │
│ │ ipAddress │ 요청 IP │ │
│ │ userAgent │ 클라이언트 정보 │ │
│ │ details │ 추가 정보 (변경 전/후 값 등) │ │
│ └──────────────┴──────────────────────────────────────────────┘ │
│ │
│ 예시 로그: │
│ { │
│ "timestamp": "2026-03-29T10:30:00Z", │
│ "userId": "user-123", │
│ "action": "UPDATE_USER_ROLE", │
│ "resource": "user/456", │
│ "result": "SUCCESS", │
│ "ip": "211.xxx.xxx.xxx", │
│ "details": { "before": "USER", "after": "ADMIN" } │
│ } │
│ │
└─────────────────────────────────────────────────────────────────────┘
|
4.3 Spring에서 감사 로깅 구현
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
| // 1. 감사 이벤트 AOP
@Aspect
@Component
@RequiredArgsConstructor
public class AuditLogAspect {
private final AuditLogRepository auditLogRepository;
@Around("@annotation(auditable)")
public Object audit(ProceedingJoinPoint joinPoint, Auditable auditable) throws Throwable {
String userId = SecurityContextHolder.getContext()
.getAuthentication().getName();
String action = auditable.action();
try {
Object result = joinPoint.proceed();
saveLog(userId, action, "SUCCESS", null);
return result;
} catch (Exception e) {
saveLog(userId, action, "FAILURE", e.getMessage());
throw e;
}
}
private void saveLog(String userId, String action, String result, String detail) {
AuditLog log = AuditLog.builder()
.userId(userId)
.action(action)
.result(result)
.detail(detail)
.ipAddress(getClientIp())
.timestamp(LocalDateTime.now())
.build();
auditLogRepository.save(log);
}
}
// 2. 커스텀 어노테이션
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Auditable {
String action();
}
// 3. 사용
@Auditable(action = "DELETE_USER")
@DeleteMapping("/users/{id}")
public void deleteUser(@PathVariable Long id) { ... }
|
5. Rate Limiting
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
| ┌─────────────────────────────────────────────────────────────────────┐
│ Rate Limiting 전략 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ Rate Limiting = API 호출 횟수 제한으로 남용/DDoS 방어 │
│ │
│ 주요 알고리즘: │
│ ┌──────────────────┬──────────────────────────────────────────┐ │
│ │ Fixed Window │ 고정 시간 윈도우마다 카운트 초기화 │ │
│ │ │ 예: 1분에 100회 │ │
│ │ │ 단점: 윈도우 경계에서 2배 요청 가능 │ │
│ ├──────────────────┼──────────────────────────────────────────┤ │
│ │ Sliding Window │ 현재 시점 기준 이전 N초 내 카운트 │ │
│ │ │ Fixed Window의 경계 문제 해결 │ │
│ ├──────────────────┼──────────────────────────────────────────┤ │
│ │ Token Bucket │ 일정 속도로 토큰 충전, 요청 시 소비 │ │
│ │ │ 버스트 허용 + 평균 속도 제한 │ │
│ ├──────────────────┼──────────────────────────────────────────┤ │
│ │ Leaky Bucket │ 일정 속도로만 요청 처리 (큐에 대기) │ │
│ │ │ 출력 속도 일정 │ │
│ └──────────────────┴──────────────────────────────────────────┘ │
│ │
│ 구현 방법: │
│ • Redis INCR + EXPIRE (분산 환경) │
│ • Bucket4j (Java 라이브러리) │
│ • API Gateway 단에서 처리 (AWS API Gateway, Kong) │
│ • Nginx limit_req 모듈 │
│ │
│ 응답 헤더 (표준): │
│ X-RateLimit-Limit: 100 ← 전체 허용 횟수 │
│ X-RateLimit-Remaining: 42 ← 남은 횟수 │
│ X-RateLimit-Reset: 1711700000 ← 초기화 시각 │
│ → 초과 시 429 Too Many Requests 응답 │
│ │
└─────────────────────────────────────────────────────────────────────┘
|
6. 보안 체크리스트 종합
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
| ┌─────────────────────────────────────────────────────────────────────┐
│ 백엔드 보안 체크리스트 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ [전송 계층] │
│ ☐ HTTPS 강제 (HSTS 헤더) │
│ ☐ TLS 1.2 이상만 허용 │
│ ☐ 안전한 Cipher Suite 설정 │
│ │
│ [응답 헤더] │
│ ☐ Content-Security-Policy 설정 │
│ ☐ X-Frame-Options: DENY │
│ ☐ X-Content-Type-Options: nosniff │
│ ☐ Referrer-Policy 설정 │
│ │
│ [인증/인가] │
│ ☐ 비밀번호 BCrypt 해싱 │
│ ☐ JWT 안전한 저장 (HttpOnly Cookie) │
│ ☐ RBAC 또는 적절한 권한 모델 적용 │
│ ☐ 최소 권한 원칙 │
│ │
│ [입력 검증] │
│ ☐ SQL Injection 방지 (Prepared Statement) │
│ ☐ XSS 방지 (출력 이스케이프 + CSP) │
│ ☐ CSRF 토큰 적용 │
│ ☐ 입력값 화이트리스트 검증 │
│ │
│ [API 보호] │
│ ☐ Rate Limiting 적용 │
│ ☐ CORS 출처 제한 (와일드카드 * 금지) │
│ ☐ 민감 정보 응답에서 제외 │
│ │
│ [로깅/모니터링] │
│ ☐ 보안 이벤트 감사 로깅 │
│ ☐ 로그인 실패 횟수 제한 + 알림 │
│ ☐ 비정상 패턴 탐지 (같은 IP에서 대량 실패 등) │
│ │
└─────────────────────────────────────────────────────────────────────┘
|
면접에서 자주 묻는 질문
Q1. CSP(Content-Security-Policy)란 무엇이고 왜 필요한가요?
CSP는 브라우저에게 허용된 리소스 출처를 알려주는 HTTP 응답 헤더입니다. script-src 'self'로 설정하면 같은 출처의 스크립트만 실행되고, 외부에서 주입된 악성 스크립트는 차단됩니다. XSS 공격의 핵심 방어 수단으로, 서버 측 입력 검증과 함께 사용하면 다중 방어층을 구성할 수 있습니다.
Q2. RBAC과 ACL의 차이는?
RBAC은 사용자에게 역할(ADMIN, USER 등)을 부여하고 역할에 권한을 매핑합니다. 관리가 간단하여 대부분의 웹 서비스에 적합합니다. ACL은 리소스마다 개별적으로 접근 권한 목록을 관리합니다. 파일 시스템이나 Google Docs처럼 리소스별 세밀한 권한 제어가 필요할 때 사용합니다. 일반적인 웹 서비스에는 RBAC이, 리소스 단위 공유가 필요하면 ACL이 적합합니다.
Q3. HSTS가 뭔가요?
HTTP Strict Transport Security의 약자로, 브라우저에게 해당 도메인은 항상 HTTPS로만 접속하라고 지시하는 헤더입니다. Strict-Transport-Security: max-age=31536000; includeSubDomains으로 설정하면, 사용자가 HTTP로 접속해도 브라우저가 자동으로 HTTPS로 전환합니다. 중간자 공격(MITM)에서 HTTP 다운그레이드를 방지합니다.
Q4. 보안 이벤트 로깅에서 반드시 기록해야 할 항목은?
로그인 성공/실패, 권한 변경, 민감 데이터 접근, 관리자 작업, 인가 실패는 반드시 기록해야 합니다. 각 로그에는 타임스탬프, 사용자 ID, 수행 작업, 대상 리소스, 결과(성공/실패), IP 주소를 포함합니다. 보안 사고 시 원인 추적(포렌식)과 이상 행동 탐지의 기반이 됩니다.
Q5. Rate Limiting은 왜 필요하고 어떻게 구현하나요?
API 남용, 브루트포스 공격, DDoS를 방지하기 위해 일정 시간 내 API 호출 횟수를 제한합니다. 분산 환경에서는 Redis의 INCR + EXPIRE로 구현하며, Token Bucket 알고리즘이 일반적입니다. 초과 시 429 Too Many Requests를 응답하고, X-RateLimit-Remaining 헤더로 남은 횟수를 알려줍니다.
Q6. Clickjacking이 뭐고 어떻게 방어하나요?
공격자가 투명한 iframe으로 정상 사이트를 겹쳐놓고, 사용자가 보이는 버튼을 클릭하면 실제로는 iframe 안의 다른 동작을 실행하게 하는 공격입니다. X-Frame-Options: DENY 또는 SAMEORIGIN 헤더를 설정하면 해당 페이지가 iframe에 삽입되는 것을 차단합니다. CSP의 frame-ancestors 디렉티브로도 방어 가능합니다.
Q7. CORS에서 Preflight 요청은 언제 발생하나요?
단순 요청(Simple Request) 조건을 벗어날 때 Preflight(OPTIONS) 요청이 발생합니다. 단순 요청 조건은 메서드가 GET/HEAD/POST이고, Content-Type이 application/x-www-form-urlencoded, multipart/form-data, text/plain 중 하나이며, 커스텀 헤더가 없는 경우입니다. Authorization 헤더를 사용하거나, Content-Type이 application/json이면 Preflight가 발생합니다.
핵심 정리: Security Headers(CSP, HSTS, X-Frame-Options)로 브라우저 단 방어를 구축하고, RBAC으로 역할 기반 인가를 구현하고, 감사 로깅으로 보안 이벤트를 추적하고, Rate Limiting으로 API 남용을 방지하는 것이 실무 보안의 핵심이다.