장쫄깃 기술블로그

[Spring Security] CSRF Token 사용 본문

Spring Framework/Spring Security

[Spring Security] CSRF Token 사용

장쫄깃 2025. 4. 3. 18:25
728x90

 


CSRF(Cross-Site Request Forgery) 란?


CSRF(크로스 사이트 요청 위조, Cross-Site Request Forgery)는 사용자가 신뢰하는 웹사이트를 대상으로 악의적인 요청을 보내도록 속이는 공격이다.

 

🔷 CSRF 공격 개념

  1. 사용자가 사이트 A에 로그인하여 세션을 유지
  2. 공격자가 사용자를 속여 사이트 A로 특정 요청을 보내도록 유도 (예: 계좌 이체, 비밀번호 변경)
  3. 사용자의 인증 정보(세션, 쿠키 등)가 자동으로 포함되어 서버가 요청을 정상적인 사용자 요청으로 오인
  4. 피해자가 원치 않는 작업이 실행됨

 

🔷 CSRF 방어 방법

  • CSRF 토큰 사용: 요청마다 임의의 토큰을 포함해 서버에서 검증
  • SameSite 속성 설정: 쿠키가 타 사이트 요청에서 전송되지 않도록 설정
  • Referer / Origin 헤더 검사: 요청이 신뢰할 수 있는 출처에서 왔는지 확인
  • CORS 정책 강화: 신뢰할 수 없는 도메인의 요청 차단

이번 글에선 CSRF 방어 방법 중 CSRF Token 사용에 대해 알아보도록 하겠다.

 

 

Spring Security의 CSRF 보호 작동 방식


  1. 서버가 클라이언트에 CSRF 토큰을 제공
    • 기본적으로 CSRF 토큰은 세션 또는 쿠키(XSRF-TOKEN)에 저장
    • 서버가 렌더링한 <form>에는 _csrf 값을 hidden 필드로 추가
  2. 클라이언트가 요청 시 CSRF 토큰을 포함
    • POST, PUT, DELETE 요청 시, <input hidden> 필드 또는 X-CSRF-TOKEN 헤더에 CSRF 토큰을 포함
  3. 서버에서 CSRF 토큰을 검증
    • 요청에 포함된 CSRF 토큰이 서버에 저장된 값과 일치하는지 확인
    • 불일치하거나 없으면 403 Forbidden 응답을 반환

 

 

Form 방식에서 CSRF 사용 방법


Spring Security는 기본적으로 CSRF 보호를 활성화한다. 따라서 별도의 설정이 필요하지 않지만, CSRF 토큰을 직접 사용하려면 클라이언트와 서버 간에 토큰을 주고받는 과정이 필요하다.

 

1. CSRF Token Repository 구현

CSRF Token을 세션에 저장하려면 HttpSessionCsrfTokenRepository를, 쿠키에 저장하려면 CookieCsrfTokenRepository를, Token을 지연 생성하여 성능 최적화를 도모하려면 LazyCsrfTokenRepository를 사용한다.

 

이 중 CSRF 방어 목적으로는 쿠키보다 안전하면서 보편적으로 사용하는 세션 기반 CSRF Token을 사용해 보겠다.

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository;

@Configuration
public class HttpSessionCsrfToken {

    @Bean
    HttpSessionCsrfTokenRepository httpSessionCsrfTokenRepository() {
        HttpSessionCsrfTokenRepository csrfRepository = new HttpSessionCsrfTokenRepository();

        // HTTP 헤더 name 설정
        // 기본값: "X-CSRF-TOKEN"
        csrfRepository.setHeaderName("X-CSRF-TOKEN");
        // URL 파라미터에서 토큰에 대응되는 name 설정
        // 기본값: "_csrf"
        csrfRepository.setParameterName("_csrf");
        // 세션에서 토큰을 인덱싱 하는 문자열을 설정
        // 기본값: "org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository.CSRF_TOKEN"
        csrfRepository.setSessionAttributeName("CSRF_TOKEN");

        return csrfRepository;
    }

}

 

HTTP 요청 시 사용할 헤더 name, 파라미터 name, 세션 name을 설정한다.

 

2. CSRF 토큰 활성화 및 설정

Spring Security의 CSRF 설정을 확인하고 필요에 따라 커스터마이징 한다.

/**
 * Spring Security Config
 */
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {

    private final HttpSessionCsrfTokenRepository httpSessionCsrfTokenRepository;

    // ...

    @Bean
    public SecurityFilterChain config(HttpSecurity http, HandlerMappingIntrospector introspector) throws Exception {
        // ...

        // csrf token repository 설정
        http.csrf(csrf -> csrf.csrfTokenRepository(httpSessionCsrfTokenRepository));

        // build
        return http.build();
    }
    
    // ...

}

 

3. Form에서 CSRF Token 추가

<form action="/login" method="post">
        <!-- csrf token -->
        <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">

        <div class="input-group">
            <label for="username">사용자 이름</label>
            <input type="text" id="username" name="username" required>
        </div>
        <div class="input-group">
            <label for="password">비밀번호</label>
            <input type="password" id="password" name="password" required>
        </div>
        <button type="submit" class="login-button">로그인</button>
        <a href="/regist" class="signup-link">회원가입</a>
    </form>

 

 

테스트


1. CSRF Token이 없는 경우

CSRF Token이 없는 경우 403 에러를 반환한다.

 

2. CSRF Token이 있는 경우

CSRF Token이 있는 경우 정상적으로 요청을 수행한다.

 

📌 같은 서버에서의 요청이라도 CSRF 토큰이 필요한 이유


Spring Security는 "세션 기반이더라도 CSRF 공격을 방어해야 한다" 는 보안 원칙을 따른다. 즉, 같은 서버에서 전송하는 요청이라도 POST, PUT, DELETE 요청에는 CSRF 토큰이 반드시 필요하다.

 

🛑 WHY?

  • CSRF 공격은 동일 출처(Same-Origin)에서도 발생할 수 있음
  • 공격자가 사용자의 인증 세션을 악용하여 요청을 위조할 수 있음
  • 예를 들어, 로그인된 사용자가 악성 사이트를 방문하면 자동으로 요청이 전송될 수도 있음

 

정리하며


REST API의 경우 JWT나 OAuth2 같은 토큰 기반 인증을 사용하기 때문에 CSRF 보호를 비활성화하는 것이 일반적이다. 때문에, 이번 글에서는 html form에서 CSRF Token 사용 방법에 대해서만 설명했다.

 

자세한 코드는 깃허브를 참고하길 바란다.

 

링크 : https://github.com/JangDaeHyeok/SpringBoot-Security

 

GitHub - JangDaeHyeok/SpringBoot-Security

Contribute to JangDaeHyeok/SpringBoot-Security development by creating an account on GitHub.

github.com

 

728x90