이전 포스트에서는 시큐리티 사용 시 개발 편의를 위해 csfr 를 disable() 했었다.
하지만 보안을 위해 disable을 해제하니 403이 두둥! 하고 나타났다.
그렇다면 csfr 이 무엇인지 먼저 알아보자!
CSRF(Cross-Site Request Forgery)는 악의적인 웹사이트가 사용자의 브라우저를 속여 인증된 사용자의 자격 증명으로 요청을 보냄으로써 발생하는 공격이다.
공격 시나리오
✔ 사용자가 A 웹사이트에 로그인하여 세션 쿠키를 보유.
✔ 공격자가 만든 악성 웹사이트에 접속.
✔ 브라우저는 A 웹사이트로 POST 요청(예: 계좌 송금)을 자동으로 보냄.
✔ A 웹사이트는 세션 쿠키를 확인하고, 사용자의 요청으로 착각해 요청을 처리.
CSRF 토큰은 CSRF 공격을 방지하기 위해 도입된 추가적인 보안 메커니즘으로,
무작위 토큰을 생성해 요청마다 고유한 CSRF 토큰을 생성해 클라이언트에게 전달한다.
클라이언크는 POST 요청을 보낼 때 토큰을 함께 전송해야 하며, 서버는 요청에 포함된 토큰과 세션에 저장된 토큰이 일치하는지 확인한다.
만약 토큰이 없거나 잘못된 경우 요청을 거부(403)한다.
(1) 토큰 생성 및 전달
Spring Security는 요청마다 CSRF 토큰을 생성하고, 이 토큰은 HTML 폼에 숨겨진 필드로 추가되거나, AJAX 요청 시 HTTP 헤더로 전달된다.
예: Spring Security가 HTML에 CSRF 토큰을 제공
<input type="hidden" name="_csrf" value="random-csrf-token-value" />
(2) 클라이언트에서 토큰 전송
클라이언트는 POST 요청 시 CSRF 토큰을 함께 전송합니다.
HTML 폼에서는 <input> 필드로 전송.
AJAX 요청에서는 HTTP 헤더로 전송.
(3) 서버에서 토큰 검증
폼을 통해 상태 변경 요청을 보낼 때 CSRF 토큰을 숨겨진 필드로 포함한다.
<form action="/submit" method="post">
<input type="hidden" name="_csrf" value="${_csrf.token}" />
<button type="submit">Submit</button>
</form>
AJAX 요청 시 CSRF 토큰은 HTTP 헤더에 포함하여 전송한다.
const csrfToken = document.querySelector('meta[name="_csrf"]').content;
fetch('/submit', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': csrfToken
},
body: JSON.stringify({ key: 'value' })
});
위 방법 또는 아래 방법으로 진행
<meta name="_csrf" content="${_csrf.token}">
<meta name="_csrf_header" content="${_csrf.headerName}">
var csrfToken = document.querySelector('meta[name="_csrf"]').content;
var csrfHeader = document.querySelector('meta[name="_csrf_header"]').content;
$.ajax({
type : type,
url : url,
data : formData,
contentType : false,
processData : false,
enctype : 'multipart/form-data',
beforeSend: function(xhr) {
xhr.setRequestHeader(csrfHeader, csrfToken);
},
success : function(response){
fileSuccess(response);
},error : function(e){
console.log(e);
}
});
이렇게 해주면 CSRF 를 활성화 하더라도 403 에러 없이 사용할 수 있다!