FrameWork/Django

Authentication

mansoorrr 2024. 7. 25. 16:39

Authentication은 인증받는 것을 의미한다.

Django는 세션 베이스로 로그인을 하게되면 세션과 쿠키를 생성한다.

그리고 user가 django페이지에 접속하면 자동으로 쿠키를 읽는다.

 

자동으로 해주지만 커스텀 하기 위해 여러 방법을 알아본다.

 

 

[** django Authentication **]

  • 장고에서 사용하는 default Authentication은 다음과 같다.
  • Authentication은 무조건 views.py들 보다 먼저 실행된다.
#---------- config/settings.py

<생략...>
#Authentication
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.SessionAuthentication',
    ]
}

 

 

 

[** Custom Authentication **]

  • 나만의 Authentication Class를 만들어 settings에 적용 가능
    • user만 리턴하면 됨. 그러면 view에서 사용하는 reques.user에 사용 가능
    • rest_framwork.authentication.BaseAuthentication을 상속받아야 함
      • authenticate를 override해야함
      • 인자로 self, request를 받음 -> request에는 user에 해당하는 값이 없음
      • user를 찾아 반환하거나 없으면 None을 반환
'''
[Authetication 만들기]
1. 클래스 만들기
2. authenticate override
3. settings에 적용

'''
#---------- config/authentication.py

class TrustMeBroAuthentication(BaseAuthentication):

    def authenticate(self, request):
        print(request.headers)
        return None #None을 반환했으므로 인증이 되지 않을 것임
        
        
#---------- config/settings.py
#Authentication
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.SessionAuthentication',
        'config.authentication.TrustMeBroAuthentication',
    ]
}

custom authentication에 return을 None으로 설정한 결과: 인증 못함

  • user를 반환할 수 있도록 코드 작성
class TrustMeBroAuthentication(BaseAuthentication):

    def authenticate(self, request):
        username = request.headers.get('Trust-me')
        if not username:
            return None
        try:
            user = User.objects.get(username=username) #user찾기
            return (user, None) #user반환
        except User.DoesNotExist:
            raise AuthenticationFailed(f"not user: {username}")

 

 

 

[** Token Authentication **]

  • django는 기본적으로 TokenAuthentication이 내장되어 있음
  • TokenAuthentication이 적용되는 방식
    • django가 사용자에게  토큰을 부여 (id와 password를 통해)
    • 사용자는 부여받은 토큰을 통해 django에 접속
    • django는 사용자별로 부여한 토큰과 사용자가 접속할때 입력한 토큰을 비교하여 일치할 경우 데이터 반환
  • 사용방법 및 순서
    • settings.py에 rest_framework.authtoken 추가(INSTALLED_APPS)
    • settings.py에 REST_FRAMEWORK에 rest_framework.authentication.TokenAuthentication추가
    • python manage.py migrate: admin패널에 토큰 테이블 생성
    • 토큰을 얻기 위한 url 추가
      • postman을 활용해 해당 url로 post요청 보내면 토큰이 생성됨(body에 id, password입력)
    • 부여받은 토큰으로 데이터 확인
      • postman을 통해 /api/v1/users/me로 get방식 요청
      • header에 부여받은 인증 토큰을 담아서 보내야 함
        • key: Authorization
        • value: Token <부여받은 토큰>
        • value 작성시 Token입력 후 띄어쓰기 꼭 해야함
#-------------------- config/settings.py

# Application definition
THIRD_PARTY_APPS = [
    "rest_framework",
    "rest_framework.authtoken",
]

# Application definition
CUSTOM_APPS = [    
    'users.apps.UsersConfig',
    'rooms.apps.RoomsConfig',
    'common.apps.CommonConfig',
    'experiences.apps.ExperiencesConfig',
    'categories.apps.CategoriesConfig',
    'reviews.apps.ReviewsConfig',
    'wishlists.apps.WishlistsConfig',
    'bookings.apps.BookingsConfig',
    'medias.apps.MediasConfig',
	'direct_messages.apps.DirectMessagesConfig',
]

SYSTEM_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

INSTALLED_APPS = SYSTEM_APPS + THIRD_PARTY_APPS + CUSTOM_APPS

#Authentication
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.SessionAuthentication',
        'config.authentication.TrustMeBroAuthentication',
        'rest_framework.authentication.TokenAuthentication', #토큰 인증
    ]
}
#----------- users/urls.py
from rest_framework.authtoken.views import obtain_auth_token #토큰 받기 위한 rest_framework의 패키지

urlpatterns = [
    path('', Users.as_view()),
    path('me/', Me.as_view()),
    path('<str:username>', PublicUser.as_view()),
    path('change-password/', ChangePassword.as_view()),
    path('log-in/', LogIn.as_view()),
    path('log-out/', LogOut.as_view()),
    path('token-login/', obtain_auth_token), #토큰을 받기 위한 url 추가
]

 

 

[** JWT Authentication **]

  • Json Web token
  • db에 공간을 잡아먹지 않음
  • 암호화된 정보를 토큰으로 만들어 유저에게 부여
  • 유저가 부여받은 토큰을 django에게 주면 토큰을 해독하여 데이터 보여줌
  • pyJWT 라이브러리 사용
    • encode: 사용자 정보를 토큰화하여 전달
      • payload: user의 어떤 정보를 token화 할 것인지 정함(중요한 정보로 만들면 안됨)
      • key: django settings.py에 있는 secret key
      • algorithm: 토큰 변환에 사용되는 알고리즘
    • decode: 토큰을 사용자 정보로 변환
  • 별도의 라이브러리는 없으므로 직접 토큰만들어 로그인 하는 로직 구현(Encode)
##---------------- ENCODE

#---------- users/urls.py
urlpatterns = [
    path('', Users.as_view()),
    path('me/', Me.as_view()),
    path('<str:username>', PublicUser.as_view()),
    path('change-password/', ChangePassword.as_view()),
    path('log-in/', LogIn.as_view()),
    path('log-out/', LogOut.as_view()),
    path('token-login/', obtain_auth_token),
    path('jwt-login/', JwtLogin.as_view()), #jwt-login
]

#---------- users/views.py
import jwt
from django.conf import settings

class JwtLogin(APIView):

	def post(self, request):
    	username = request.data.get('username')
        password = request.data.get('password')
        if not username or not password:
        	raise ParseError('입력하세요')
            
        user = authenticate(
        	request=request,
            username=username,
            password=password
        )
        
        if not user:
        	raise ParseError('그런사람 없어요')        
        #토큰 부여를 위한 encode
        token = jwt.encode(
        	payload={'pk':user.pk},
            key=settings.SECRET_KEY,
            algorithm='HS256'
        )
        return Response({'token':token})

 

 

  • Decode
    • 별도의 authentication class 만들어 복호화 실시
    • config/settings.py에 등록
#---------- config/authentication.py
class JWTAuthentication(BaseAuthentication):
    def authenticate(self, request):
        token = request.headers.get('Jwt')
        if not token: #토큰 없으면 유저정보 없음
            raise None
        #복호화
        decoded = jwt.decode(
            token,
            key=settings.SECRET_KEY,
            algorithms=['HS256'],
        )
        pk = decoded.get('pk') #encoding시에 user의 어떤 정보를 토큰으로 만들것인가(pk)
        if not pk:
            raise AuthenticationFailed('Invalid Token')
        try:
            user = User.objects.get(pk=pk)
            return (user, None)
        except:
            raise AuthenticationFailed('user not found')
        
        
#---------- config/settings.py
#Authentication
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.SessionAuthentication',
        'config.authentication.TrustMeBroAuthentication',
        'rest_framework.authentication.TokenAuthentication',
        'config.authentication.JWTAuthentication',
    ]
}