TIL_230926_개인과제

Sol Lee·2023년 9월 26일
0

개인과제 - 블로그만들기(2)

오늘은 요구사항 중에 유저기능 일부 작업함

JWT로 회원가입/로그인/로그아웃 구현하기

(1) 기본 세팅

setting.py

INSTALLED_APPS = [
    ...
    'rest_framework',
]

# 라이브러리 추가 - REST_FRAMEWORK 부분이 없으면 그부분부터 전체 추가
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_simplejwt.authentication.JWTAuthentication',
    )
}

# JWT 설정 - 잘 모르겠고 일단 필요해 보이는 것만 추가해 봄
SIMPLE_JWT = {
    'ACCESS_TOKEN_LIFETIME': timedelta(minutes=30),
    'REFRESH_TOKEN_LIFETIME': timedelta(days=7),

    "ALGORITHM": "HS256",
    "SIGNING_KEY": SECRET_KEY, 

    'SLIDING_TOKEN_REFRESH_EXP_CLAIM': 'refresh_exp',
    'SLIDING_TOKEN_LIFETIME': timedelta(minutes=5),
    'SLIDING_TOKEN_REFRESH_LIFETIME': timedelta(days=1),

urls.py

from django.urls import path
from users import views
from rest_framework_simplejwt.views import (
    TokenObtainPairView,
    TokenRefreshView,
)

urlpatterns = [
    path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
    path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'), 
]

(2) 커스텀 유저앱 생성

장고 공식 문서 참조

models.py

from django.db import models
from django.utils import timezone
from django.contrib.auth.models import (
    BaseUserManager, AbstractBaseUser
)

class UserManager(BaseUserManager):

    def create_user(self, email, username, password=None):
				""" 일반 유저 생성 """
        if not email:
            raise ValueError('Users must have an email address')

        user = self.model(
            email=self.normalize_email(email),
						username=username,
        )

        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_superuser(self, email, username, password=None):
				""" 슈퍼 유저 생성 """
        user = self.create_user(
            email,
            username=username,
            password=password,
        )
        user.is_admin = True
        user.save(using=self._db)
        return user

class User(AbstractBaseUser):
    email = models.EmailField(
        verbose_name='Email address',
        max_length=255,
        unique=True,
    )
    ... # 필드 추가

    objects = UserManager()

    USERNAME_FIELD = 'username' # 로그인시 아이디로 사용할 필드
    REQUIRED_FIELDS = []

    def __str__(self):
        return self.username

    @property
    def is_staff(self):
        "Is the user a member of staff?"
        return self.is_admin

admin.py

from django import forms
from django.contrib import admin
from django.contrib.auth.models import Group
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.forms import ReadOnlyPasswordHashField
from django.core.exceptions import ValidationError

from users.models import User

class UserCreationForm(forms.ModelForm):
    password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
    password2 = forms.CharField(
        label='Password confirmation', widget=forms.PasswordInput)

    class Meta:
        model = User
        fields = ('email','username',)

    def clean_password2(self):
        password1 = self.cleaned_data.get("password1")
        password2 = self.cleaned_data.get("password2")
        if password1 and password2 and password1 != password2:
            raise ValidationError("Passwords don't match")
        return password2

    def save(self, commit=True):
        user = super().save(commit=False)
        user.set_password(self.cleaned_data["password1"])
        if commit:
            user.save()
        return user

class UserChangeForm(forms.ModelForm):
    password = ReadOnlyPasswordHashField()

    class Meta:
        model = User
        fields = ('email', 'username', 'password', 'is_active', 'is_admin',)

class UserAdmin(BaseUserAdmin):
    form = UserChangeForm
    add_form = UserCreationForm
		# admin page 설정
    list_display = ('email', 'username', 'is_admin') # 리스트에서 보여줄 필드
    list_filter = ('is_admin',) # 필터 설정
    fieldsets = (
        (None, {'fields': ('email','username', 'password')}),
        (('Personal info'), {'fields': ('nickname', 'fullname', 'date_of_birth')}),
        ('Permissions', {'fields': ('is_admin',)}),
    )
    add_fieldsets = (
        (None, {
            'classes': ('wide',),
            'fields': ('email', 'username', 'fullname', 'nickname', 'date_of_birth', 'password1', 'password2'),
        }),
    )
    search_fields = ('email', 'username')
    ordering = ('email',)
    filter_horizontal = ()

admin.site.register(User, UserAdmin)
admin.site.unregister(Group)

(3) 시리얼라이저 생성

serializer.py

from rest_framework import serializers
from users.models import User

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = "__all__"

    def create(self, validated_data):
        user = super().create(validated_data)
        pw = user.password
        user.set_password(pw)
        user.save()
        return user

(4) 회원가입

urls.py

from django.urls import path
from users import views
from rest_framework_simplejwt.views import (
    TokenObtainPairView,
    TokenRefreshView,
)

urlpatterns = [
    path('signup/', views.UserView.as_view(), name='user_view'),  # post-회원가입
		...
]

views.py

class UserView(APIView):
    def post(self, request):
        serializer = UserSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response({"message": "register successs"}, status=status.HTTP_201_CREATED)
        else:
            return Response({"message": f"${serializer.errors}"}, status=status.HTTP_400_BAD_REQUEST)

(5) 로그인/로그아웃

urls.py

from django.urls import path
from users import views
from rest_framework_simplejwt.views import (
    TokenObtainPairView,
    TokenRefreshView,
)

urlpatterns = [
    path('signup/', views.UserView.as_view(), name='user_view'),  # post-회원가입
    path('auth/', views.UserAuthView.as_view(),name='user_view'),  # post-로그인/delete-로그아웃/get-유저확인
		...
]

views.py

class UserAuthView(APIView):
    # 로그인
    def post(self, request):
        # 유저 인증
        user = authenticate(
            username=request.data.get("username"), password=request.data.get("password")
        )
        # 이미 회원가입 된 유저일 때
        if user is not None:
            serializer = UserSerializer(user)

            # jwt 토큰 접근
            token = TokenObtainPairSerializer.get_token(user)
            refresh_token = str(token)
            access_token = str(token.access_token)
            res = Response(
                {
                    "user": serializer.data,
                    "message": "login success",
                },
                status=status.HTTP_200_OK,
            )
            # jwt 토큰 => 쿠키에 저장
            res.set_cookie("access", access_token, httponly=True)
            res.set_cookie("refresh", refresh_token, httponly=True)
            return res
        else:
            return Response(status=status.HTTP_400_BAD_REQUEST)

    # 로그아웃
    def delete(self, request):
        # 쿠키에 저장된 토큰 삭제 => 로그아웃 처리
        response = Response({
            "message": "Logout success"
        }, status=status.HTTP_202_ACCEPTED)
        response.delete_cookie("access")
        response.delete_cookie("refresh")
        return response

(6) 본인 정보 조회

views.py

# 유저 정보 확인
    def get(self, request):
        try:
            access = request.COOKIES['access']  # 쿠키에서 access token 가져오기
            payload = jwt.decode(access, SECRET_KEY, algorithms=['HS256'])  # 디코드하기
            pk = payload.get('user_id')  # 페이로드에서 user_id 가져오기
            user = get_object_or_404(User, pk=pk) # 유저 데이터중에 user_id와 primary key(id)가 같은 게 있는지 확인
            serializer = UserSerializer(instance=user)  # 시리얼라이즈
            return Response(serializer.data, status=status.HTTP_200_OK)

        except (jwt.exceptions.InvalidTokenError):
            # 사용 불가능한 토큰일 때
            return Response(status=status.HTTP_400_BAD_REQUEST)

강의랑 블로그 참고해가면서 했는데 아직 잘 모르겠다.

테스트

회원가입

로그인

본인 정보 조회

참고
[DRF] JWT 인증을 사용한 회원가입&로그인
[Django] JWT#2, 장고 JWT토큰 사용하기, payload, Pyjwt, 장고 로그인 유지
https://www.django-rest-framework.org/#installation
https://docs.djangoproject.com/en/4.1/topics/auth/customizing/
https://django-rest-framework-simplejwt.readthedocs.io/en/latest/getting_started.html

profile
직업: 개발자가 되고 싶은 오레오 집사

0개의 댓글