개발

CSRF (Cross-Site Request Forgery)란?

피터JK 2025. 3. 25. 11:45
728x90

CSRF(Cross-Site Request Forgery, 사이트 간 요청 위조)는 사용자가 의도하지 않은 요청을 특정 웹사이트에 보내도록 유도하는 공격 기법입니다.

CSRF 공격 방식

  1. 사용자가 사이트 A(예: https://bank.com)에 로그인하여 인증된 상태가 됨.
  2. 공격자가 사용자를 사이트 B(악성 사이트)로 유도함.
  3. 사이트 B가 사이트 A로 요청을 보냄 (예: 송금 요청).
  4. 사용자가 로그인된 상태이므로, 사이트 A는 요청을 정상적인 것으로 인식하고 실행함.

즉, 공격자가 사용자 모르게 인증된 요청을 서버에 보내는 방식입니다.


Spring Framework & Spring Boot에서 CSRF 설정

Spring Security에서는 CSRF 공격을 방지하기 위해 CSRF 토큰 기반 검증을 기본적으로 활성화한다.


1. Spring Security에서 CSRF 기본 동작

Spring Security가 활성화되면, 모든 POST, PUT, DELETE 요청은 CSRF 토큰을 필요로 함.
즉, CSRF 보호가 기본적으로 활성화됨.

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .csrf(csrf -> csrf // CSRF 보호 활성화 (기본값)
                .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) // 쿠키 기반 CSRF 설정
            )
            .authorizeHttpRequests(auth -> auth
                .anyRequest().authenticated()
            );

        return http.build();
    }
}

위 설정이 적용되면

  • 모든 POST, PUT, DELETE 요청에 대해 CSRF 토큰이 필요함.
  • CSRF 토큰은 XSRF-TOKEN이라는 이름으로 쿠키에 저장됨.
  • 프론트엔드에서 요청 시 X-XSRF-TOKEN 헤더에 추가해야 함.

CSRF 토큰을 프론트엔드에서 사용하는 방법 (JavaScript)

const csrfToken = document.cookie.split('; ')
    .find(row => row.startsWith('XSRF-TOKEN='))
    ?.split('=')[1];

fetch('/api/protected-endpoint', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        'X-XSRF-TOKEN': csrfToken
    },
    body: JSON.stringify({ data: 'value' })
});

2. CSRF 설정 해제 (비추천 ⚠️)

CSRF 보호를 해제하는 경우, 보안 취약점이 발생할 수 있음.
단, API 서버가 완전히 stateless (JWT, OAuth 사용) 한 경우 해제할 수 있음.

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .csrf(csrf -> csrf.disable()) // CSRF 보호 비활성화
            .authorizeHttpRequests(auth -> auth
                .anyRequest().permitAll()
            );

        return http.build();
    }
}

3. CSRF 토큰 설정

3.1 CSRF 토큰을 헤더 대신 쿠키로 설정 (Spring Boot 기본값)

Spring Boot에서는 CSRF 토큰을 쿠키에 저장하여 클라이언트에서 쉽게 접근할 수 있도록 설정 가능.

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .csrf(csrf -> csrf
                .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) // CSRF 토큰을 쿠키에 저장
            )
            .authorizeHttpRequests(auth -> auth
                .anyRequest().authenticated()
            );

        return http.build();
    }
}

이렇게 하면:

  • CSRF 토큰이 XSRF-TOKEN 쿠키로 저장됨.
  • 클라이언트는 쿠키에서 토큰을 읽어 X-XSRF-TOKEN 헤더에 추가해야 함.

 

3. 2 CSRF 토큰을 헤더 대신 세션으로 설정

  • Spring Security 5.7+부터 WebSecurityConfigurerAdapter가 deprecated되었으며, 대신 SecurityFilterChain을 사용하여 보안 설정을 구성합니다.
  • SecurityFilterChain에서 HttpSessionCsrfTokenRepository 설정
    CSRF 토큰을 세션(HttpSession)에 저장하는 설정을 적용하려면 아래와 같이 SecurityFilterChain을 사용합니다.
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository;
import org.springframework.security.web.csrf.CsrfTokenRepository;

@Configuration
public class SecurityConfig {

    // CSRF 토큰을 HttpSession에 저장하는 Bean
    @Bean
    public CsrfTokenRepository csrfTokenRepository() {
        HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
        repository.setSessionAttributeName("_csrf"); // 기본 세션 속성 이름 (_csrf)
        return repository;
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .csrf(csrf -> csrf
                .csrfTokenRepository(csrfTokenRepository()) // HttpSession에 CSRF 토큰 저장
            )
            .authorizeHttpRequests(auth -> auth
                .anyRequest().authenticated()
            );

        return http.build();
    }
}

4. 특정 요청에 대해서만 CSRF 검증 비활성화

예: API 요청(/api/**********************************************)은 CSRF 검증을 하지 않도록 설정

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .csrf(csrf -> csrf
                .ignoringRequestMatchers("/api/**") // `/api/`로 시작하는 요청은 CSRF 보호 해제
            )
            .authorizeHttpRequests(auth -> auth
                .anyRequest().authenticated()
            );

        return http.build();
    }
}

5. CSRF 토큰을 숨김 입력 필드로 전달 (Thymeleaf 예제)

Spring MVC (Thymeleaf)에서는 CSRF 토큰을 폼(hidden input) 필드로 전달 가능.

HTML Form에서 CSRF 토큰 사용 예제

<form action="/submit" method="post">
    <input type="hidden" name="_csrf" value="${_csrf.token}" />
    <button type="submit">전송</button>
</form>

Spring Security가 자동으로 CSRF 토큰을 Model에 추가하므로, 위처럼 hidden input을 사용하면 자동으로 보호됨.

_csrf 값이 변조 되거나 값이 없으면 403에러가 발생한다.


정리

설정  코드 예제 설명
기본 CSRF 보호 http.csrf().enable(); (기본값) CSRF 토큰이 필요함
CSRF 토큰을 쿠키로 저장 csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) XSRF-TOKEN 쿠키에 저장
CSRF 해제 (위험) http.csrf().disable(); API 서버에서만 추천
특정 요청 제외 ignoringRequestMatchers("/api/**") /api/**는 CSRF 검증 제외
폼에서 CSRF 토큰 사용 <input type="hidden" name="_csrf" value="${_csrf.token}"/> HTML form에 CSRF 토큰 추가

결론

  1. Spring Security는 기본적으로 CSRF 보호 활성화됨.
  2. 모든 POST, PUT, DELETE 요청은 CSRF 토큰을 포함해야 함.
  3. API 서버(JWT 기반)에서는 http.csrf().disable(); 가능.
  4. CSRF 토큰을 쿠키(XSRF-TOKEN********************************************)에 저장하여 클라이언트에서 쉽게 사용 가능.
  5. Form에서는 hidden input 필드로 CSRF 토큰을 전달해야 함.
  •  CSRF 보호는 보안상 중요한 요소이므로, 비활성화하지 않는 것이 좋습니다!
728x90