로그인 인증 시나리오
- 사용자 계정은 외부의 인증 서버를 통해 인증한다.
- 인증 성공하면 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에 포함시켜야 합니다. 자세한 방법은 여기를 참고하세요.
'Django' 카테고리의 다른 글
PyCharm manage.py 메뉴가 사라진 경우 (0) | 2018.06.21 |
---|---|
Django logger 설정 (0) | 2018.05.10 |
Django Rest Framework 인증 관련 설정 (0) | 2017.01.23 |
[DRF] prefetch_related와 SerializerMethodField (0) | 2016.12.16 |
윈도우에서 django mysql 설정하기 (0) | 2015.12.27 |