[Django] Forbidden (403) : CSRF cookie not set 에러 해결하기

[Django] Forbidden (403) : CSRF cookie not set 에러 해결하기

참고 자료 : (장고 공식 홈페이지 csrf 관련 문서 바로가기)[https://docs.djangoproject.com/en/5.0/ref/csrf/]

기본적으로 CSRF가 무엇인지 알고 갑시다.

CSRF(Cross Site Request Forgery)란?

CSRF는 웹 보안 취약점의 하나로, 악의적인 웹사이트가 사용자를 대신하여 요청을 전송하게 하는 공격입니다. 이를 통해 공격자는 사용자가 인증된 상태에서 수행할 수 있는 행동을 무단으로 수행하게 할 수 있습니다.

예를 들면 사용자가 A라는 웹 사이트에 로그인을 통해 인증된 상태가 되었을 때, 사용자의 실수로 악의적인 웹 사이트 B 웹 사이트를 클릭한다면 B 웹 사이트에서 CSRF를 통해 이미 인증된 상태의 A 웹 사이트에 사용자가 원하지 않는 요청을 마구 보낼 수 있습니다. 이때 A 웹 사이트 서버에서는 해당 요청이 사용자가 직접 보낸 요청인지 아닌지를 판단하기 어렵기 때문에 문제가 발생합니다.

CSRF 방지 방법

위에서 설명한 이유로 인해 여러 CSRF 방지 방법이 존재합니다. 이때 가장 흔하게 사용되는 방법인 CSRF 토큰을 사용하는 것입니다. CSRF 토큰을 통해 요청이 신뢰할 수 있는 웹 사이트에서 온 것인지 판단하는 것입니다. 위의 상황을 다시 예로 들자면, B 웹 사이트는 A 웹 사이트에서 신뢰할 수 있는 사이트로 분류되어 있지 않기 때문에 CSRF 토큰을 발행할 수 없고, 결과적으로 사용자가 A 웹 사이트에 로그인하여 인증된 상태라 하여도 B 웹 사이트에서 A 웹 사이트로 보낸 요청은 CSRF 토큰이 없거나 일치하지 않기 때문에 모두 차단됩니다.

CSRF 토큰값이 일치하지 않거나 없어서 발생하는 문제입니다.

하지만 저는 CSRF 토큰값을 이용했음에도 불구하고 위 문제가 발생했습니다. 일단 저의 상황입니다.

상황

일반적으로 장고만 사용해서 개발할 경우 화면은 template을 사용하면 되고 간단하게 {\% csrf_token \%}라는 탬플릿 태그를 활용해 csrf 토큰값을 사용할 수 있습니다. 하지만 저는 화면단을 리액트로 구현해 백엔드(장고)와 프론트엔드(리액트)를 나누어 개발하고 있습니다. 이 상황에서 form태그를 제출하는 과정에 csrf 토큰 에러가 발생한 것입니다. 저는 처음에 다음과 같은 방법으로 리액트에서 장고의 CSRF 토큰을 사용하려 했습니다.

Django 코드

# views.py
from django.middleware.csrf import get_token

# url : http://127.0.0.1:8000/csrftoken
def get_csrf_token(request):
    csrf_token = get_token(request)
    return JsonResponse({'csrf_token': csrf_token})

# url : http://127.0.0.1:8000/test
def test(request):
    print(request.POST)
    return HttpResponse("test 성공")

위와 같은 코드를 활용해 장고에서 CSRF 토큰값을 발행한 후 Json형태로 데이터를 응답하도록 했습니다.

React 코드

<form method="post" action="http://127.0.0.1:8000/test">
    <input type="hidden" id="csrfmiddlewaretoken" name="csrfmiddlewaretoken" value=""/>
    <button type="submit">
        테스트
    </button>
</form>

<button onClick={()=>axios.get('http://127.0.0.1:8000/csrftoken')
                            .then(response=>{
                                console.log(response.data['csrf_token']);
                                const inputElemnet = document.getElementById("csrfmiddlewaretoken") as HTMLInputElement;
                                inputElemnet.value=response.data['csrf_token'];
                            })
                            .catch(error=>{
                                console.log(error);
                            })
                        }>
    CSRF 토큰 발행
</button>

CSRF 토큰을 발행하고 hidden처리되어 있는 id값 csrfmiddlewaretoken의 input 태그 value값으로 해당 CSRF 토큰값을 넣어줍니다. 그리고 해당 input 태그가 포함된 form태그를 제출하는 것이죠. 이때 CSRF 토큰값을 요청하고 input태그의 value에 넣어주는 부분까지는 문제없이 진해되었지만, input태그가 포함된 form태그를 제출하니 아래와 같은 에러가 발생한 것입니다.

Forbidden (403)
CSRF verification failed. Request aborted.

You are seeing this message because this site requires a CSRF cookie when submitting forms. This cookie is required for security reasons, to ensure that your browser is not being hijacked by third parties.

If you have configured your browser to disable cookies, please re-enable them, at least for this site, or for “same-origin” requests.

Help
Reason given for failure:

    CSRF cookie not set.

해결 방법

방법1. CSRF 사용 안하기

가장 간단한 방법은 CSRF 토큰 자체를 확인하지 않고 해당 API를 활용할 수 있게 하는 방법입니다. 하지만 개인적으로 이런 방법은 나중에 문제가 생길 수 있는 가능성을 열어두는 행위라고 생각하여 추천하지는 않지만 지금 당장 문제를 해결하고 싶다면 가장 쉽고 삐르게 적용할 수 있는 방법입니다.

Django 코드 수정

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

# url : http://127.0.0.1:8000/csrftoken
def get_csrf_token(request):
    csrf_token = get_token(request)
    return JsonResponse({'csrf_token': csrf_token})

# url : http://127.0.0.1:8000/test
@csrf_exempt
def test(request):
    print(request.POST)
    return HttpResponse("test 성공")

위 코드와 같이 장고의 csrf_exempt 데코레이터를 활용하면 CSRF 토큰 인증없이 해당 API를 활용할 수 있게 됩니다. 즉, test라고 정의되어 있는 함수를 get_csrf_token함수를 사용하여 CSRF 토큰을 발행하지 않아도 사용할 수 있다는 뜻입니다.

results matching ""

    No results matching ""