CSRF 공격
사용자가 로그인된 상태에서 공격자가 유도한 악성 요청을 브라우저가 자동으로 실행해 서버에 전달하는 공격.
사용자는 자신도 모르게 중요한 요청(비밀번호 변경, 송금 등)을 실행하게 됨.
예시
- 사용자가 securebank.com에 로그인하고 쿠키에 세션이 저장됨.
Cookie: JSESSIONID=abc123
- 공격자가 만든 사이트 evil.com에 아래 코드가 있다면
- 사용자가 evil.com 접속 → 브라우저가 쿠키 포함 요청 자동 전송 → 공격 성공.
<img src="https://securebank.com/transfer?amount=10000&to=999999" />
Spring Security를 활용한 CSRF 방어
- Spring Security는 CSRF 방어 기능이 기본 활성화됨.
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().enable()
.authorizeRequests()
.antMatchers("/transfer").authenticated()
.anyRequest().permitAll();
}
}
- 폼에서 CSRF 토큰 사용하기
- Spring은 서버 세션에 CSRF 토큰을 생성하고, 폼에 자동 삽입 가능.
# Thymeleaf 예시
<form action="/transfer" method="post">
<input type="text" name="toAccount" />
<input type="number" name="amount" />
<input type="submit" value="Transfer" />
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
</form>
- REST API에서 CSRF 토큰 사용
- SPA, AJAX 환경에서 CSRF 토큰을 헤더로 전송해야 함
- REST API + SPA 환경에서 CSRF 토큰을 사용하는 방식은 서버와 클라이언트가 분리되어 있고,
- 폼(form) 제출이 아닌 JavaScript를 통한 AJAX 호출이기 때문에, CSRF 토큰을 헤더에 담아서 전송해야 함
- JavaScript (fetch, axios 등) 기반 요청에서는 직접 토큰을 가져와서 헤더에 담아야 서버가 검증할 수 있음.
- Spring Security는 이를 고려해 CSRF 토큰을 클라이언트가 쉽게 사용할 수 있도록 HTML 메타태그 또는 쿠키로 제공
흐름 요약
1. 서버가 최초 HTML을 렌더링할 때 CSRF 토큰 정보를 함께 내려줌
2. 클라이언트는 <meta> 태그나 쿠키에서 CSRF 토큰을 읽음
3. AJAX 요청 시 해당 토큰을 헤더에 추가하여 전송
4. 서버는 요청에 포함된 토큰과 세션의 토큰이 일치하는지 검증
# HTML
<meta name="_csrf" content="${_csrf.token}" />
<meta name="_csrf_header" content="${_csrf.headerName}" />
# 기본적으로 CSRF 토큰은 활성화돼 있으며,
# HttpSessionCsrfTokenRepository를 사용하면 HTML 메타태그를 통해 내려보낼 수 있음.
#Java
@Bean
public CsrfTokenRepository csrfTokenRepository() {
CookieCsrfTokenRepository repository = CookieCsrfTokenRepository.withHttpOnlyFalse();
repository.setCookieName("XSRF-TOKEN");
repository.setHeaderName("X-XSRF-TOKEN");
return repository;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.csrfTokenRepository(csrfTokenRepository());
}
# Javascript
const token = document.querySelector('meta[name="_csrf"]').content;
const header = document.querySelector('meta[name="_csrf_header"]').content;
fetch('/api/transfer', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
[header]: token
},
body: JSON.stringify({ toAccount: '999999', amount: 10000 })
});
- SameSite 쿠키 설정
- Spring Boot에서 SameSite=Strict 설정 시, 크로스 도메인 요청에 쿠키 전송 차단됨.
server.servlet.session.cookie.same-site=strict