Django

인증 구현하기

r잡초처럼 2023. 5. 14. 11:41

Django 인증 절차를 Custom 하고 싶었다.

나는 모든 View에서 인증 절차를 처리하는 AuthenticationMiddleware를 조작하거나 AUTHENTICATION_CLASSES를 재정의해서 request.user를 확인하는 절차를 처리할 수 있다. 나는 AuthenticationMiddleware 보다는 AUTHENTICATION_CLASSES 를 재정의하고자 했다. 그 이유는 AuthenticationMiddleware는 상위의 위치하는 레이어이기 때문에 이걸 섣부르게 커스텀했다가는 내가 인지 못하는 부작용이 발생할 거 같았다.

AuthenticationMiddleware 

AuthenticationMiddleware 는 각 View에서 인증을 위한 절차를 검사하는 middleware이다. 여기서 만약 custom 하고 싶다면 process_header를 재정의하면 된다.

class AuthenticationMiddleware(MiddlewareMixin):
    def process_request(self, request):
        if not hasattr(request, "session"):
            raise ImproperlyConfigured(
                "The Django authentication middleware requires session "
                "middleware to be installed. Edit your MIDDLEWARE setting to "
                "insert "
                "'django.contrib.sessions.middleware.SessionMiddleware' before "
                "'django.contrib.auth.middleware.AuthenticationMiddleware'."
            )
        request.user = SimpleLazyObject(lambda: get_user(request))

AUTHENTICATION_CLASSES 

내가 커스텀하고자 했던 인증 시스템은 Login 할 때 심어놓았던 cookie 를 백엔드 서버에서 읽어 들여 인증을 거치고자 했다. 굳이 프런트에서 인증 헤더에 심지 않고 백엔드에서 모두 처리하려고 했던 이유는 다음과 같았다. 

  1. 어차피 프론트에서도 cookie 값을 읽어 들여 헤더에 token을 심어서 보내는데, 백엔드에서 하면 프런트 쪽에서 할 일이 줄어들 거 같다.
  2. 보안적인 측면에서도 브라우에서 header 를 조작해 봤자 백엔드는 cookie를 바라보고 있기 때문에 header를 조작하는 것은 의미가 없다.

이러한 이유로 백엔드에서 cookie 를 통한 로그인 기능을 구현하고자 했다. 

우선 login 을 할 때 cookie에서 옵션을 통해 브라우저에서 조작할 수 없도록 했다.

response.set_cookie(
    domain="example.com",
    maxage=60 * 60,
    secure="True",
    samesite="Lax",
    httponly=True,
)

그리고 JWTAuthentication 를 상속받은 다음 커스텀 했다. JWTAuthentication에서는 authenticate 메서드에서 header를 읽어 들이고 있으므로 이 부분을 고쳤다.

  • 원래의 JWTAuthentication:
# JWTAuthentication


class JWTAuthentication(authentication.BaseAuthentication):
    """
    An authentication plugin that authenticates requests through a JSON web
    token provided in a request header.
    """

   ...
   
    def authenticate(self, request):
        header = self.get_header(request)
        if header is None:
            return None

        raw_token = self.get_raw_token(header)
        if raw_token is None:
            return None

        validated_token = self.get_validated_token(raw_token)

        return self.get_user(validated_token), validated_token
  • 커스텀:
class CustomJWTAuthentication(JWTAuthentication):
    def get_cookie(self, request):
        # 이 부분에서 cookie 를 읽어들이도록 만들었다.

    def authenticate(self, request):
        cookie = self.get_cookie(request)
        if cookie is None:
            return None

        raw_token = self.get_raw_token(cookie)
        if raw_token is None:
            return None

        validated_token = self.get_validated_token(raw_token)

        return self.get_user(validated_token), validated_token

커스텀을 하면서 errorMessage 도 사내 컨벤션에 따라 넣고자 했다.

    ...
    def get_validated_token(self, raw_token):
        ...
        raise InvalidToken(
            {
                "code": self.ERROR_CODE.FAIL_TOKEN_AUTH.code,  # 새롭게 커스텀한 부분
                "message": self.ERROR_CODE.FAIL_TOKEN_AUTH.message,  # 새롭게 커스텀한 부분
                "detail": {
                    "info": _(
                        "Given token not valid for any token type",
                    ),
                    "messages": messages,
                },
            }
        )

이렇게 인증 기능을 커스텀하면서 Django 의 middleware와 인증 기능을 이해하는 좋은 기회가 되었다.