Django

Django Custom Auth (로그인 인증, API 인증)

behonestar 2017. 1. 24. 13:54

로그인 인증 시나리오

  • 사용자 계정은 외부의 인증 서버를 통해 인증한다.
  • 인증 성공하면 Django Session에 User 등록하고 Token 반환한다.


사용자 계정 인증 모듈

외부 인증서버를 통해 username, password를 인증하는 custom 인증 모듈을 만듭니다.

# -*- coding: utf-8 -*-

from django.conf import settings

from django.contrib.auth import get_user_model

from cognitoidentityprovider.cognito_user import CognitoUser


import requests


User = get_user_model()



class MyBackend(object):

    """

    Return User instance containing access_token

    """


    def authenticate(self, username=None, password=None, **kwargs):

        if not username or not password:

            return None


        r = requests.get('https://auth.server.com/', auth=(username, password))

        if r.status_code != 200:

            return None


        try:

            user = User.objects.get(username=username)

        except User.DoesNotExist:

            return None


        return user


    def get_user(self, user_id):

        UserModel = get_user_model()

        try:

            return UserModel._default_manager.get(pk=user_id)

        except UserModel.DoesNotExist:

            return None



settings.py

django.contrib.auth.authenticate 함수를 호출하면, MyBackend를 통해 인증하도록 정의합니다.

AUTHENTICATION_BACKENDS = (

    'myapp.backend.MyBackend',

    'django.contrib.auth.backends.ModelBackend',

)


로그인

# -*- coding: utf-8 -*-

from __future__ import unicode_literals

from django.contrib.auth import authenticate, login

from rest_framework import status

from rest_framework import viewsets

from rest_framework.response import Response


import jwt

import time


JWT_SECRET = 'mysecretkey'



class UserViewSet(viewsets.ViewSet):

    authentication_classes = ()

    permission_classes = (AllowAny, )


    def signin(self, request):

        username = request.data.get('username')

        password = request.data.get('password')


        # 사용자 계정 인증 (MyBackend 사용됨)

        user = authenticate(username=username, password=password)

        if user is None:

            return Response(serializer.data, status=status.HTTP_400_BAD_REQUEST)


        # token 생성

        expire_ts = int(time.time()) + 3600

        payload = {'username': username, 'expire':expire_ts}

        token = jwt.encode(payload, JWT_SECRET, algorithm='HS256')


        if getattr(settings, 'REST_SESSION_LOGIN', True):

            # django session에 user 등록

            login(self.request, user)


        return Response(token)





API 인증 시나리오

  • API를 호출하면 Session 인증을 수행한다.
  • Session 인증에 실패하면 Token 인증을 수행한다.
    • token에서 expire를 추출하여 만료 여부를 검사한다.
    • token에서 username을 추출하여 user 객체를 반환한다.


Token 인증 모듈

# -*- coding: utf-8 -*-

from rest_framework import exceptions

from django.contrib.auth import get_user_model


import jwt

import time


User = get_user_model()

JWT_SECRET = 'mysecretkey'



def get_authorization_header(request):

    auth = request.META.get('HTTP_AUTHORIZATION', b'')

    if isinstance(auth, type('')):

        auth = auth.encode(HTTP_HEADER_ENCODING)

    return auth


class MyTokenAuthentication():

    def authenticate(self, request):

        token = get_authorization_header(request)

        if not token:

            return None


        # token 디코딩

        payload = jwt.decode(token, JWT_SECRET, algorithms=['HS256'])


        # token 만료 확인

        expire = payload.get('expire')

        if int(time.time()) > expire:

            return None


        # user 객체

        username = payload.get('username')

        if not username:

            return None


        try:

            user = User.objects.get(username=username)

        except User.DoesNotExist:

            raise exceptions.AuthenticationFailed('No such user')


        return (user, token)


settings.py

Rest framework의 APIView에 의해 호출될 Session 인증과 Token 인증 모듈을 정의한다.

REST_FRAMEWORK = {

    'DEFAULT_AUTHENTICATION_CLASSES': (   # Session 인증 실패하면 Token 인증

        'rest_framework.authentication.SessionAuthentication',

        'myapp.authentication.MyTokenAuthentication',

    ),

    ...

}


API 구현

# -*- coding: utf-8 -*-

from rest_framework import viewsets

from rest_framework.response import Response



class ApiViewSet(viewsets.ViewSet):

    def myapi(self, request):

        return Response('authentication & permission passed')



정리

유저가 로그인에 성공하면 Django Session에 해당 유저가 등록되고 Token도 리턴됩니다.

API를 호출할 때 Session 인증을 사용할지 Token 인증을 사용할지는 유저의 몫입니다.

서버는 양쪽의 인증을 모두 대비하면 됩니다.


참고로 AJAX로 API를 호출할 때 Session 인증을 사용하려면 CSRF 토큰을 POST Body에 포함시키거나, X-CSRFToken을 Header에 포함시켜야 합니다. 자세한 방법은 여기를 참고하세요.