Published on

Django에서 401 Unauthorized 오류 해결하기

Authors

Django에서 401 Unauthorized 오류가 발생하는 원인은 다양하지만, 이번 사례에서는 프론트엔드의 잘못된 API 요청이 문제였습니다.

지속적으로 문제가 되는 API 소스를 들여다봐도, 문제를 알 수가 없었고 백엔드에서 401이 발생할 수 있는 모든 원인을 파악하고자 했습니다.

그 과정에서 다른 원인들을 찾아보며 발견한 것들을 정리합니다.

1. 401 Unauthorized 가 어디서 발생하는지?

프론트엔드에서 API 요청이 올바르게 전송되지 않거나 인증 관련 헤더가 누락되면 401 Unauthorized 오류가 발생할 수 있습니다. 따라서 요청의 헤더를 확인하고, 필요하면 디버깅을 진행해야 합니다.

1) Django의 인증(Authentication) 관련 401 발생 원인

Django에서 401 에러를 발생시키는 대표적인 원인

  • permission_classes = []로 설정했지만 401 발생authentication_classes도 명시적으로 비워야 합니다.

하지만 프론트엔드에서는 분명히 빈 리스트로 설정했음에도 401 오류가 발생한다고 했습니다. 따라서 다른 설정들을 점검할 필요가 있었습니다.

class MyAPIView(generics.GenericAPIView):
    authentication_classes = []
    permission_classes = []

2) Django Rest Framework의 기본 설정이 401을 반환하는 경우

  • Django Rest Framework의 기본 DEFAULT_AUTHENTICATION_CLASSES 설정이 적용될 수 있습니다. → SessionAuthentication이 적용되면, Django는 CSRF 보호를 요구하여 401을 반환할 수 있습니다.
REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES": [
        "rest_framework.authentication.SessionAuthentication",
        "rest_framework.authentication.TokenAuthentication",
    ],
}

하지만, 이것도 상관이 없다고 생각했습니다.

다른 컴퓨터의 terminal에서, postman에서 그 외 API 요청(swagger)에서도 정상적으로 요청이 이루어져서 이상함을 느꼈습니다.

3) CSRF 관련 401 에러

CSRF 관련 주요 원인

  • Django는 SessionAuthentication을 사용할 때 CSRF 토큰을 요구합니다. → 프론트에서 credentials: "include" 없이 요청하면 401이 발생할 가능성이 있습니다.

프론트엔드에서 요청을 보낼 때 CSRF 토큰을 포함하는지는 별도로 확인하지 않았습니다. 하지만, 작업중인 코드베이스(세팅)이 너무 복잡하고, 신뢰성이 떨어진 상태였고 이를 확인하기 위해 백엔드에서 CSRF 토큰을 비활성화하는 방법을 찾아보았습니다.

CSRF 때문에 발생하는 일반적인 에러는 403(Forbidden) 입니다. 하지만 인증 방식(SessionAuthentication)과 연관되면 401(Unauthorized)도 발생할 수 있습니다.

그래서 아래 처럼

  • API 요청이 CSRF 예외 처리되지 않은 경우 → API에 대해 CSRF 보호를 비활성화할 수 있습니다.

csrf_exempt를 사용하여 CSRF 보호를 비활성화할 수 있습니다.

from django.views.decorators.csrf import csrf_exempt
from django.utils.decorators import method_decorator

@method_decorator(csrf_exempt, name="dispatch")
class MyAPIView(generics.GenericAPIView):
    authentication_classes = []
    permission_classes = []


# 혹은 이렇게도 가능
@csrf_exempt
def my_view(request):
    pass

4) Next.js의 캐싱 문제와 401 발생

  • Next.js는 기본적으로 **SSG(Static Site Generation)**을 통해 캐싱된 데이터를 제공합니다.
  • API 요청 시 캐시된 응답이 반환될 경우, 백엔드의 최신 응답이 반영되지 않아 401이 발생할 수 있습니다. 프론트엔드에서는 API 요청이 정상적으로 이루어졌다고 했지만, 요청이 브라우저 캐시에 의해 변조되지 않았는지 확인해야 했습니다.

해결 방법

  • Cache-Control: no-store를 추가하여 최신 응답을 강제합니다.
axios.get("https://some url", {
    headers: {
        "Cache-Control": "no-store",
        "Pragma": "no-cache"
    },
    params: { t: Date.now() } // 캐시 우회
});

2. 연관된 개념

결과적으로 백엔드 코드를 아무것도 수정하지 않고, 프론트 수정으로 문제는 해결되었습니다. 하지만 아래 개념들이 연관되어 있어, 이를 함께 이해하면 더 좋을 것 같다고 느꼈습니다.

  1. Django의 인증 시스템
  • SessionAuthentication, TokenAuthentication, JWT Authentication
  • Django의 DEFAULT_AUTHENTICATION_CLASSES 동작 방식
  1. CSRF 보호와 예외 처리
  • csrf_exempt, X-CSRFToken, withCredentials
  1. CORS 설정 및 Next.js의 API 요청 방식 (CORS 는 절대 원인이 아닐거라고 생각했지만, 해결되었다는 연락이 늦게 왔다면 확인했을 것 입니다.)
  • CORS_ALLOWED_ORIGINS, CORS_ALLOW_CREDENTIALS
  • getStaticProps, getServerSideProps, fetch의 캐싱 방식 차이

3. 참고 링크

  • hongreat 블로그의 글을 봐주셔서 감사합니다!^^
  • 내용에 잘못된 부분이나 의문점이 있으시다면 댓글 부탁 & 환영 합니다~!
  • (하단의 버튼을 누르시면 댓글을 보거나 작성할 수 있습니다.)
Buy Me A Coffee