본문 바로가기

문돌이 존버/Django 스터디

장고(Django), Cross Site Request Forgery

반응형
본 글은 Holix의 "리액트와 함께 장고 시작하기 Complete" 강의를 듣고 작성한 일지입니다.

CSRF(Cross Site Request Forgery)란 사용자가 의도치 않게 게시판에 글을 작성하거나 쇼핑을 하게 하는 등의 공격을 의미합니다. 특정 웹사이트가 유저의 웹브라우저를 신용하는 상태를 노린 것입니다.

요청을 받는 서버에선 공격을 막기 위해 Token을 통해 체크합니다. 장고는 POST 요청에 한해 CsrfViewMiddleware을 통해 확인하는데요, Token 값이 없거나 유효하지 않으면 403 Forbidden을 응답하게 됩니다.

settings.py

처리 순서는 아래와 같습니다.

1. 클라이언트의 Form GET 요청으로 서버가 입력 Form을 보여줄 때 CSRF Token 값을 할당
   (CSRF Token은 User마다 다르며 계속 변경)
2. 입력 Form을 통해 Token 값이 전달되면 Token 유효성 검증

CSRF Token을 할당하는 코드는 아래처럼 {% csrf_token %} 으로 설정하면 됩니다.

<form action="" method="post" enctype="multipart/form_data">
    {% csrf_token %}
    <table>
        {{ form.as_table }} <!-- form만 써도 as_table 자동 구현 -->
    </table>
    <input type='submit' value='저장' />
</form>

주의사항으로는 CSRF Token과 유저인증 Token / JWT(JSON Web Token) 은 다른 개념이라는 것입니다. CSRF Token은 단지 현재 요청이 유효한지에 대한 Token입니다.

참고로 CSRF Token 체크 기능을 끌 수도 있습니다.

# views.py
from django.views.decorators.csrf import csrf_exempt

# csrf token을 확인하지 않는 장식자
@csrf_exempt
def post_new(request):
    if request.method == 'POST':
        form = PostForm(request.Post, request.FILES)
        ...

하지만 가급적이면 체크 기능을 살리는 것이 좋습니다. 기본적으로 제공되는 보안기능일 뿐 아니라 유지하는 데도 비용이 거의 들지 않기 때문입니다.

다만 Jquery를 사용하여 POST 요청을 할 때는 CSRF Token 관련 에러가 발생할 수 있습니다. 이때 아래와 같이 설정하여 관련 에러를 피할 수 있습니다.

/* static/jquery.csrf.js */
function getCookie(name) {
    let cookieValue = null;
    if (document.cookie && document.cookie !== '') {
        const cookies = document.cookie.split(';');
        for (let i = 0; i < cookies.length; i++) {
            const cookie = cookies[i].trim();
            // Does this cookie string begin with the name we want?
            if (cookie.substring(0, name.length + 1) === (name + '=')) {
                cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                break;
            }
        }
    }
    return cookieValue;
}
const csrftoken = getCookie('csrftoken');

const request = new Request(
    /* URL */,
    {
        method: 'POST',
        headers: {'X-CSRFToken': csrftoken},
        mode: 'same-origin' // Do not send CSRF token to another domain.
    }
);
fetch(request).then(function(response) {
    // ...
});
<!-- layout.html -->
<!doctype html>
<html lang='ko'>
<head>
    <meta charset='utf-8' />
    ...
    <script src="{% static 'jquery.csrf.js' %}"></script>
https://docs.djangoproject.com/en/4.0/ref/csrf/

마지막으로 앱 API에서는 CSRF Token을 꺼두는 것이 좋다고 합니다. django-rest-framework의 APIView에서는 csrf_exempt가 이미 적용되어 있습니다. 따라서 CSRF Token은 앱이 아닌 웹에서의 클라이언트의 유효성 검증을 위한 수단이라고 생각하면 되겠습니다.

728x90
반응형