본문 바로가기

Spring Boot/Security

주요 웹 보안 이슈 & Security 방어 전략 : CSRF

CSRF(Cross-Site Request Forgery)

 

 - CSRF 공격의 개념, 위험성

 - Security의 기본 CSRF 보호 메커니즘

 - CsrfFilter와 CsrfToken의 동작 방식

 - http.csrf() 설정을 통한 CSRF 방어

 - 실무에서 CSRF 보호를 커스터마이징 하는법

 - 토큰 생성, 저장, 전달, 검증 과정의 내부 동작 이해


CSRF(Cross-Site Request Forgery)

1. 개념

CSRF는 사용자가 의도하지 않은 요청을 공격자가 위조하여 서버에 보내도록 하는 공격 기법임

피해자는 이미 로그인된 세션을 가진 상태이므로, 서버는 공격자가 보낸 요청을 정상 요청으로 오인하게 된다

CSRF 공격 히스토리

 

결과적으로 사용자는 모르는 사이에 계정 정보 변경, 송금, 글 작성 등의 행위가 발생할 수 있다

 

 

2. 예시

온라인 뱅킹 : 공격자가 만든 페이지에 접속시 자동으로 송금 요청이 서버에 전달된다

게시판 : 로그인된 사용자가 공격자 페이지를 방문하면 자동으로 글이 작성된다


Spring Security의 CSRF 보호 기본 메커니즘

SecurityFilterChain에 꼭 넣는 .csrf() 메서드에 관한 내용임

 

1. 기본 동작

  • Security는 기본적으로 상태 유지 세션 기반 애플리케이션에서 CSRF 보호를 활성화한다
  • 서버는 요청시 CSRF 토큰을 검증하여, 클라이언트가 의도적으로 요청을 보냈는지 확인한다

 

2. CsrfFilter와 CsrfToken

  • CsrfFilter : 모든 요청에 대해 CSRF 토큰을 검증하는 필터임
    사용자가 보낸 요청에 _csrf 파라미터나 헤더 값이 없는 경우, AccessDeniedException을 발생시킴
  • CsrfToken : 서버가 세션별로 생성하여 클라이언트에 전달하는 토큰임
    이 토큰은 매 요청마다 클라이언트가 전달해야하며, 서버는 저장된 토큰과 비교하여 일치 여부를 검사한다

 

(1) 토큰 생성 과정

  • Spring Security는 사용자가 최초로 세션을 생성하거나 페이지를 요청할 때 고유한 난수 기반 토큰을 생성함
  • 이 토큰은 HttpSessionCsrfTokenRepository(기본 구현체) 또는 CookieCsrfTokenRepository 에 저장된다
  • 난수는 SecureRanom 기반으로 생성되어 예측 불가능하며, 위조가 어렵다

SecurityConfig의 메서드로 넣었다

package com.b1uffer.customfiltertest.config;

@Configuration
public class SecurityConfig {

    @Bean
    public CsrfTokenRepository csrfTokenRepository() {
        HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
        repository.setParameterName("_csrf"); // _csrf
        repository.setHeaderName("X-CSRF-TOKEN"); // X-CSRF-TOKEN
        return repository;
    }
}

 

 

(2) 토큰 전달 방식

  • 폼 hidden 필드 : 서버에서 렌더링 시 _csrf 값을 자동으로 삽입함
  • AJAX / SPA : 응답 헤더 또는 meta 태그로 전달 후, 요청시 X-CSRF-TOKEN 헤더에 포함함
  • Double-submit 쿠키 : 토큰을 쿠키와 헤더 모두에 담아 서버에서 일치 여부를 검증함

 

(3) 검증 방식

  1. 요청이 들어오면 CsrfFilter가 동작한다
  2. 요청에 포함된 토큰(_csrf 필드 또는 헤더값)을 추출함
  3. 저장소(HttpSession 또는 Cookie)에 보관된 토큰과 비교한다
  4. 일치하면 정상 요청, 불일치 또는 누락시 AccessDeniedException 발생

공격자는 올바른 토큰을 알 수 없으므로 위조 요청은 차단된다

# CsrfFilter 동작 순서
 - loadToken() - > 저장소에서 토큰을 조회함
 - 없으면 generateToken() 후 saveToken()
 - 요청 메서드가 POST/PUT/DELETE/PATCH 이면, 토큰을 검증
 - 토큰 불일치시 - > AccessDeniedException 발생

 

 

(4) 다양한 클라이언트 예시

 

* JavaScript Fetch API

const token = document.querySelector('meta[name="_csrf"]').getAttribute('content');
const header = document.querySelector('meta[name="_csrf_header"]').getAttribute('content');

fetch('/transfer', {
	method: 'POST',
    headers: {
    	[header]: token,
        'Content-Type': 'application/json'
    },
    body: JSON.stringify({amount: 1000})
});

 

 

* REST API 클라이언트 (Postman)

POST /transfer HTTP/1.1
Host: localhost:8080
Content-Type: application/json
X-CSRF-TOKEN: 3a12f5c9-1234-4e99-82bb-9083f1e7f9aa

{
    "amount": 5000
}

 

 

 

(5) 보강 전략

  • Origin / Referer 검증 : 요청 헤더로 동일 출처인지 확인함
  • SameSite 쿠키 : Lax 또는 Strict 설정으로 크로스 도메인 요청을 차단함
  • XSS 방어 : 토큰 탈취 방지를 위해 필수적임

CSRF 보호 설정 및 커스터마이징

1. 기본 설정

Spring Security 6.x 부터는 CSRF가 기본적으로 활성화되어있다

@Configuration
public class SecurityConfig {
    @Bean
    public SecurityFilterChain defaultChain(HttpSecurity http) throws Exception {
        http
                .csrf(csrf -> csrf.enable()) // 기본값, 메서드는 없음
                .authorizeHttpRequests(auth -> auth.anyRequest().authenticated());

        return http.build();
    }

 

 

 

2. 특정 경로 예외 처리하기

@Configuration
public class SecurityConfig {
    @Bean
    public SecurityFilterChain defaultChain(HttpSecurity http) throws Exception {
        http
                .csrf(csrf -> csrf.ignoringRequestMatchers("/api/**")) // 특정 경로 예외처리
                .authorizeHttpRequests(auth -> auth.anyRequest().authenticated());

        return http.build();
    }

 

 

 

3. Ajax 요청시 헤더 사용하기

$.ajax({
  url: '/transfer',
  type: 'POST',
  headers: {'X-CSRF-TOKEN': csrfToken},
  data: {amount: 1000}
});

 


CSRF : http.csrf() 설정하기

** Thymeleaf 의존성 추가

    // Thymeleaf
    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'

 

1. 디렉토리

src/main/java/com/example/csrf

 - config

   -- SecurityConfig.java

 - controller

   -- TransferController.java

 - templates

   -- transfer.html

 

 

2. TransferController.java

import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class TransferController {

    @GetMapping("/transfer")
    public String transferForm(Model model, CsrfToken csrfToken) {
        model.addAttribute("_csrf", csrfToken);
        return "transfer";
    }

    @PostMapping("/transfer")
    @ResponseBody
    public String doTransfer(@RequestParam int amount) {
        return "송금 완료" + amount + "원";
    }
}

 

 

 

3. transfer.html

<!DOCTYPE html>
<html>
<body>
    <form action="/transfer" method="post">
        <input type="hidden" name="_csrf" value="${_csrf.token}" />
        <input type="number" name="amount" placeholder="amount" />
        <button type="submit">submit</button>
    </form>
</body>
</html>

 

해당 폼을 통해 요청을 전송하면, CSRF 토큰이 함께 전송되어 서버에서 정상 처리된다고 함

 

이렇게

 


  • REST API 서버(JWT 기반) -> http.csrf().disable() 설정
  • 세션 기반 웹 앱 - > CSRF 보호 필수
  • Double-submit 쿠키 패턴 - > XSS 보안 병행 필요
  • SameSite, Secure, HttpOnly 쿠키 옵션 적극 활용하기

개념 설명
CSRF 공격 사용자의 세션을 악용해 의도치 않은 요청을 보내는 공격
CsrfFilter 토큰 생성 및 검증을 수행하는 필터
CsrfToken 서버가 생성해서 클라이언트에 제공하는 난수 토큰
Token 저장 HttpSession, Cookie 등
검증 방식 요청 파라미터 / 헤더 값과 저장소의 토큰 비교하기
보강 전략 Origin / Referer, SameSite, XSS 방어