«   2026/02   »
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
Recent Posts
Recent Comments
관리 메뉴

_z_z_

8월_2주차. Spring 주요 보안공격과 대응전략, JWT의 구조와 구성 요소 본문

쪼드잇/위클리페이퍼

8월_2주차. Spring 주요 보안공격과 대응전략, JWT의 구조와 구성 요소

hyohyo_zz 2025. 8. 11. 09:24
Q1. Spring 기반 웹 애플리케이션에서 발생할 수 있는 4가지 주요 보안 공격 (CSRF, XSS, 세션 고정, JWT 탈취)에 대해 설명하고, 각각에 대한 Spring Security 또는 일반적인 대응 전략을 설명하세요.
Q2.  JWT(JSON Web Token)의 구조와 각 구성 요소가 어떤 역할을 하는지 구체적으로 설명하세요.

 

 

1) Spring 기반 웹앱 4가지 주요 보안 공격과 대응

1-1. CSRF (Cross-Site Request Forgery)

사용자 브라우저의 자동 쿠키 전송을 악용해, 사용자가 의도하지 않은 상태 변경 요청(POST/PUT/DELETE 등)을 타 사이트에서 발생시키는 공격.

세션/쿠키 기반 인증을 쓰고, 브라우저에서 자동으로 쿠키가 붙는 API에 상태 변경이 있을 때 위험.

대응 전략
Spring Security 기본 CSRF 보호 사용(서버 렌더링/폼 기반)
폼에 CSRF 토큰 숨김 필드 삽입, 헤더로도 전송 가능.
SameSite=Lax/Strict 쿠키 사용 시 기본적인 방어에 도움.
SPA + REST API(쿠키 인증)라면 “더블 서브밋 쿠키” 패턴

서버가 XSRF-TOKEN(읽기 가능) 쿠키 발급 → 클라이언트가 헤더(X-XSRF-TOKEN)로 그대로 전송 → 서버에서 쿠키와 헤더 일치 확인.

JWT를 Authorization 헤더로 보내면 CSRF 위험 낮음
쿠키가 아니라 헤더에 토큰을 넣으니 브라우저가 자동 전송하지 않음.
단, JWT를 쿠키에 넣는 설계면 CSRF 고려 필요.

@Bean
SecurityFilterChain security(HttpSecurity http) throws Exception {
    http
        .csrf(csrf -> csrf
            // SPA 쿠키 기반이면 CookieCsrfTokenRepository 활용
            .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
            // GET에서도 토큰 강제 초기화하려면(필요 시) Resolver 사용
            .csrfTokenRequestHandler(new CsrfTokenRequestAttributeHandler())
        )
        .headers(h -> h
            .frameOptions(f -> f.sameOrigin()) // H2 콘솔 등 필요 시
        );
    return http.build();
}

 

쿠키 인증: CSRF 켜고, 더블서브밋/폼 토큰 사용.
헤더 인증(JWT Bearer): 일반적으로 CSRF 비활성(쿠키에 JWT 넣지 않는 전제).

 

1-2. XSS (Cross-Site Scripting)

입력값이 적절히 출력 인코딩되지 않고 HTML/JS 문맥으로 반영돼, 공격자 스크립트가 실행되는 취약점.
사용자 생성 컨텐츠(댓글/닉네임/게시글 등)를 페이지에 렌더링할 때 위험
리치 텍스트(HTML 허용) 처리 시 허용 리스트 미흡.

대응 전략
출력 인코딩: 템플릿 엔진에서 안전한 바인딩 사용
Thymeleaf: th:text="${value}" (자동 이스케이프), th:utext는 지양.
입력 검증 + HTML Sanitization: 허용 태그만 통과(OWASP Java HTML Sanitizer 등).
콘텐츠 보안 정책(CSP) 헤더 설정: 인라인 스크립트/임의 도메인 차단, XSS 영향 축소.
쿠키 보안 속성: HttpOnly, Secure, SameSite로 세션/토큰 탈취 난이도 상승.


스프링 설정 예시 (CSP + 기본 헤더)

@Bean
SecurityFilterChain security(HttpSecurity http) throws Exception {
    http
        .headers(h -> h
            .contentSecurityPolicy(csp -> csp
                .policyDirectives("default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'"))
            .xssProtection(x -> x.disable()) // 현대 브라우저에서 비권장, CSP로 대체
            .referrerPolicy(r -> r.policy(ReferrerPolicyHeaderWriter.ReferrerPolicy.NO_REFERRER))
        );
    return http.build();
}


Thymeleaf 예:

<!-- 안전 (이스케이프됨) -->
<span th:text="${comment.content}"></span>

<!-- 위험 (원문 HTML 삽입) -->
<span th:utext="${comment.content}"></span>

 

 

1-3. 세션 고정(Session Fixation)

공격자가 미리 발급/알고 있는 세션ID로 피해자를 로그인시키고, 이후 같은 세션ID로 권한을 가로채는 공격.
로그인 시 세션ID를 재발급하지 않거나, 세션 속성만 바꾸는 구현에서 위험.

대응 전략
로그인 시 세션ID 교체(세션 마이그레이션): Spring Security 기본 제공.
세션 속성: HttpOnly, Secure, SameSite 설정.
동시 세션 제한: 계정당 동시 로그인 수 제한.
세션 타임아웃: 적절히 짧게.

스프링 설정 예시

@Bean
SecurityFilterChain security(HttpSecurity http) throws Exception {
    http
        .sessionManagement(s -> s
            .sessionFixation(sf -> sf.migrateSession()) // 로그인 시 새 세션ID
            .maximumSessions(1) // 필요시 동시 세션 1개로 제한
        );
    return http.build();
}

 

1-4. JWT 탈취(JWT Theft)

저장소(XSS로 로컬스토리지), 전송 구간(HTTPS 미사용),

로그/모니터링 유출 등으로 액세스 토큰이 외부로 유출되는 상황.
토큰을 브라우저 저장소(localStorage/sessionStorage)에 평문 보관할 때 위험.
HTTPS 미사용, CS 로그/콘솔에 토큰 노출, 리퍼러에 토큰 실수로 포함시,
긴 만료시간, 토큰 회전 미구현시 위험

대응 전략
전송 보안: 반드시 HTTPS.
만료 짧게 + 리프레시 토큰 분리: AT는 짧게(분), RT는 길게(일/주) + 회전(rotate) & 재사용 탐지.
서버 단 블랙리스트/리보크 전략: 중요한 계정/관리자는 토큰 블랙리스트 고려.

보관 위치
가장 안전한 건 메모리 보관(새로고침 시 소실 감수) + 백엔드 세션 재발급 흐름.
쿠키 사용 시 HttpOnly, Secure, SameSite 설정 + CSRF 고려.
localStorage는 XSS에 취약 → 꼭 CSP/출력 인코딩 강화.
스코프 최소화/오디언스 검사/aud/iss 검증: 사용 범위를 최소화하고 서명/클레임 꼼꼼히 검증.
토큰 승계 방지: 중요한 변경(비번 변경/로그아웃) 시 서버에서 RT 폐기, 키 롤오버.


스프링(JWT 필터 + 무상태) 예시

@Bean
SecurityFilterChain security(HttpSecurity http, JwtAuthFilter jwtFilter) throws Exception {
    http
        .csrf(csrf -> csrf.disable()) // Authorization 헤더 기반이면 보통 비활성
        .sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
        .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class)
        .authorizeHttpRequests(auth -> auth
            .requestMatchers("/auth/**").permitAll()
            .anyRequest().authenticated()
        );
    return http.build();
}

리프레시 토큰 회전 & 재사용 탐지 개념 흐름:
클라이언트가 AT 만료 직전/후 RT로 /auth/refresh 호출
서버는 저장된 RT와 일치 + 미사용 상태인지 확인 → 새 AT/RT 발급
기존 RT는 바로 폐기하고 새 RT로 교체(회전)
폐기된 RT로 재사용 시 즉시 계정 알림/강제 로그아웃 처리

 

 

 


2) JWT(JSON Web Token)의 구조와 각 요소의 역할


형식(Compact Serialization)

<header>.<payload>.<signature>

 

모두 Base64URL로 인코딩되어 점(.)으로 연결.


2-1. Header

alg: 서명 알고리즘 (예: HS256, RS256, ES256 등)
typ: 토큰 타입(보통 "JWT")
kid: (옵션) 키 식별자, 키 롤오버 시 어떤 키로 서명했는지 식별

예)

{
  "alg": "RS256",
  "typ": "JWT",
  "kid": "2025-08-key-01"
}

 


2-2. Payload (Claims)

Registered Claims(표준)
iss(발급자), sub(주체/사용자ID), aud(대상 서비스),
exp(만료 시각), nbf(이전 사용 금지), iat(발급 시각), jti(토큰 ID, 재사용 탐지·취소용)

Public/Private Claims
서비스별 커스텀(role, scope, permissions 등). 민감정보는 피할 것.

예)

{
  "iss": "https://auth.example.com",
  "sub": "user-123",
  "aud": "api.example.com",
  "exp": 1723296000,
  "iat": 1723292400,
  "scope": "review:read review:write"
}

 


2-3. Signature

- 서명 값 = Base64UrlEncode(header) + "." + Base64UrlEncode(payload) 를

비밀키(HS256) 또는 개인키(RS256/ES256) 로 서명한 결과.

- 역할: 변조 방지(무결성) + 발급자 신뢰(인증).
대칭키(HS256): 서버들 간 공유 비밀키 필요.
비대칭키(RS/ES): 개인키로 서명/공개키로 검증, 마이크로서비스/외부 검증에 유리.

검증 시 필수 체크
서명 유효성(키/알고리즘 일치)
만료/유효 시작 시간(exp, nbf) + 발급자/대상(iss, aud)
재사용 방지(jti)는 필요 시 서버 저장소로 관리(블랙리스트/RT 재사용 탐지).