
Django 프레임워크에서 REST API를 사용하기 위해 Django를 이용하여 연습해 볼 것이다.
Django에 Login API 만들기
login app 만들기
먼저, 나는 Django에 MySQL을 연동한 상태이며, 가상환경을 사용중이다.
이전 포스트에 자세한 내용이 있다.
[Python | 가상환경] 파이썬 가상환경 venv 사용
프로젝트 마다 버전을 관리하기 위해서 가상환경을 사용합니다. python의 가상환경으로는 크게 2가지가 있습니다. 1. virtualenv 2. venv 먼저, virtualenv는 python3에서도 사용할 수 있지만... python2 버전에
0418.tistory.com
[Python | Django] 파이썬 장고 설치 (가상환경)
2022.11.22 - [Study & 교육] - [Python | 가상환경] 파이썬 가상환경 venv 사용 [Python | 가상환경] 파이썬 가상환경 venv 사용 프로젝트 마다 버전을 관리하기 위해서 가상환경을 사용합니다. python의 가상환
0418.tistory.com
Django로 app 생성
python manage.py startapp appname
app을 만들게 되면, 아래와 같이프로젝트 안에 login이라는 폴더가 생성된다.
그 다음에는
models.py에서 사용자의 ID와 PW를 저장할 모델을 만들어준다.
#login/models.py
from django.db import models
class LoginUser(models.Model) :
user_id = models.CharField(max_length=20, null=False, default=False)
user_pw = models.CharField(max_length=20, null=False, default=False)
class Meta :
db_table = 'login_user'
verbose_name = '로그인 테스트 테이블'
최대 길이가 20인 id와 pw 필드를 생성했다.
DB에 반영하기 위해서는 2가지를 해야한다.
1. settings.py의 INSTALLED_APP에 login app을 추가,
2.아래를 실행
python manage.py makemigrations
python manage.py migrate
python manage.py makemigrations [app_name]
- 마이그레이션을 생성하는 명령어.
- [app_name]을 붙이면 해당 app에 대해서만 마이그레이션을 생성
- [app_name]을 붙이지 않으면 전체 app에 대해서 마이그레이션을 생성
의도하지 않은 마이그레이션이 생성될 수도 있으니 가급적 app_name을 같이 입력하는 것이 좋다고 한다.
python manage.py migrate [app_name] [migration_name]
- 마이그레이션을 적용하는 명령어.
- app_name을 지정해서 특정 app만 migrate할 수 있다.
- migration_name을 지정해서 해당 번호(버전)의 마이그레이션을 적용할 수 있다.
-> 이전 버전으로 되돌리기 가능
*추가적인 migration 기능
python manage.py showmigrations [app_name]
# 프로젝트의 마이그레이션에 대해 적용 여부를 한 눈에 보여줌
# app_name 생략 : 전체 앱에 대해 보여줌
python manage.py sqlmigrate app_name migration_name
#해당 마이그레이션 파일이 어떤 SQL구문으로 실행되는지 보여줌.
#실제로 마이그레이션이 진행되진 않음.
주의할 점. 적용된 마이그레이션 파일은 절대로 삭제하면 안된다. 만약 마이그레이션 삭제하고 싶다면, 이전 버전으로 적용 시킨 후 삭제해야 한다.
그러니까, 다음 두 가지 중 하나를 거친 후 삭제해야 한 단 말이다.
1. python manage.py migrate app_name (0001) 이전 버전으로 적용
2. python manage.py migrate app_name zero 마이그레이션을 초기화
login_user 테이블이 생성되었다.
id, user_id, user_pw필드가 생긴 걸 볼 수 있다.
분명 나는 user_id, user_pw 필드만 생성해주었는데... id 필드는 무엇일까?
Django의 model은 기본적으로 pk값으로 사용하는 id 필드를 추가하기 때문에 생긴거라고 한다.
이제 id와 pw를 생성하는 api를 만든다.
api 만들기
아래를 쉘에서 입력해서 프레임워크를 다운받아주고,
pip install djangorestframework
settings.py의 INSTALLED_APP에 'rest_framework'추가 해준다.
login앱 폴더의 views.py 파일에 새로운 api call을 만든다.
# login/views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from .models import LoginUser
class RegistUser(APIView) :
def post(self, request):
user_id = request.data.get('user_id', "") # 클라이언트에서 올리는 user_id
user_pw = request.data.get('user_pw', "") # 클라이언트에서 올리는 user_pw
LoginUser.objects.create(user_id=user_id, user_pw=user_pw) # LoginUser 모델에 새로운 object 생성
# 클라이언트한테 내려줄 데이터 정의
data = dict(
user_id = user_id,
user_pw = user_pw
)
return Response(data=data)
RegistUser라는 클래스를 만들고,
post로 요청이 올 경우 LoginUser 모델에 user_id와 user_pw를 가지는 새로운 object를 만들게 한다.
APIView는 djangorestframework에서 제공해주는 Class 기반의 API VIEW이다.
RegistUser클래스를 APIView 상속받은것이다.
APIView에서의 장점은, 테스트 화면도 함께 보여준다는 장점이 있다.
그럼 이 RegistUser클래스가 불러져야 post 함수가 기능을할텐데,
특정 url을 통해 RegistUser를 호출할 수 있도록 urls.py를 설정하면 된다.
urls.py는 python manage.py startapp을 통해 기본으로 생성되지 않는다.
--> 생성
# login/urls.py
from django.urls import include, path
from . import views
urlpatterns = [
path('regist_user', views.RegistUser.as_view(), name='regist_user'),
]
~/regist_user를 호출하면 RegistUser를 호출할 수 있도록 설정한다.
as_view 함수의 파라미터로는, 해당 view에 전달될 인자가 된다.
그리고 root폴더에 있는 urls.py도 수정해주어야한다!
# login/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('login/', include('login.urls')),
]
include 함수를 통해 login앱에 있는 urls.py파일을 링크시킬 수 있다.
이말이 뭔가 하면,
현재 파일 구조는 다음과 같은데,
root/urls.py에 작성한 url1
root/login/urls.py에 작성한 url2
링크 시켰단 뜻은
https://localhost:8000/url1/url2로 된단 말이다.
자! 이제 한번 데이터를 주고 받아보자!
데이터 날려보기
나는 Postman을 통해서 데이터를 날려보았다.
잘 들어간 것을 볼 수 있다.
api 수정 - 유효성 검증(Validation)
ID 유효성 검증
사용자의 id는 유니크해야합니다. 하지만, 위의 RegistUser클래스의 post함수는 중복처리에 대한 로직이 없습니다.
또한, id값이 정상적인 format인지도 검증해야 하는데요.
# login/views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from .models import LoginUser
class RegistUser(APIView) :
def post(self, request):
user_id = request.data.get('user_id', "")# 클라이언트에서 올리는 user_id
user_pw = request.data.get('user_pw', "")# 클라이언트에서 올리는 user_pw
if LoginUser.objects.filter(user_id=user_id).exists() : # LoginUser 모델의 object의 id 중복 처리
data = dict(
msg = "이미 존재하는 아이디입니다."
)
return Response(data)
LoginUser.objects.create(user_id=user_id, user_pw=user_pw) # LoginUser 모델에 새로운 object 생성
# 클라이언트한테 내려줄 데이터 정의
data = dict(
user_id=user_id,
user_pw=user_pw
)
return Response(data=data)
View에서 if 문을 통해 중복값 검사 로직을 추가하였는데, 코드상에서 예외처리한다고 해도 DB에 같은 값이 들어갈 수도있다.
그래서, Model 선언부에서 user_id field에 unique값을 주어, 더 확실하게 중복을 피해보자
#login/models.py
from django.db import models
class LoginUser(models.Model) :
user_id = models.CharField(max_length=20, unique=True, null=False, default=False)
user_pw = models.CharField(max_length=20, null=False, default=False)
class Meta :
db_table = 'login_user'
verbose_name = '로그인 테스트 테이블'
이와 같은 행위를
Model Field에서 유효성 검증을 한다고 한다.
unique 옵션은 default값으로 False이다. True로 설정할 경우, DB에서 같은 형식의 데이터를 저장할 수 없게 된다.
유효성 검증의 순서는 models.py에서 먼저 하고 통과했을 경우 views.py에서 검증한다.
중복처리 외에도 특수문자 불가능, 숫자로 시작 불가능, 길이 제한 등 여러 조건을 설정해야 할 상황도 존재한다. 그건 그때 가서 구글링 해야겠다.
PW 유효성 검증
사용자의 id만큼 중요한게 pw이다.
DB를 해킹당했을 경우 타격이 클 뿐더러, 서버 개발자도 몰라야 한다.
따라서 password는 단방향 암호화를 통해 저장해야한다.
단방향 암호화란 암호화(평문 -> 암호문)만 가능하고 복호화(암호문 -> 평문)는 할 수 없다.
양방향 암호화는 암호화, 복호화 둘 다 가능하다.
Django에서 제공하는 users테이블을 사용하면, 기본적으로 password가 암호화 필드로 먹혀있다
위 암호화 기법은 단방향 암호화이다.
하지만 나는 login_user라는 테이블을 따로 만들었기 때문에, user모델과 비슷한 기능을 하도록 user_pw필드를 바꿔야 한다.
#login/models.py
from django.db import models
class LoginUser(models.Model) :
user_id = models.CharField(max_length=20, unique=True, null=False, default=False)
user_pw = models.CharField(max_length=255, null=False, default=False)
class Meta :
db_table = 'login_user'
verbose_name = '로그인 테스트 테이블'
암호화 되면 길이가 길어지기 때문에 필드의 제한 길이를 255로 바꿈
# login/views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from .models import LoginUser
from django.contrib.auth.hashers import make_password
class RegistUser(APIView) :
def post(self, request):
user_id = request.data.get('user_id', "")# 클라이언트에서 올리는 user_id
user_pw = request.data.get('user_pw', "")# 클라이언트에서 올리는 user_pw
user_pw_crypted = make_password(user_pw) # 암호화
if LoginUser.objects.filter(user_id=user_id).exists() : # LoginUser 모델의 object의 id 중복 처리
# db에 있는 값 출력할 때, 어떻게 나오는지 보기 위해 user 객체에 담음
user = LoginUser.objects.filter(user_id=user_id).first()
data = dict(
msg = "이미 존재하는 아이디입니다.",
user_id = user.user_id,
user_pw = user.user_pw
)
return Response(data)
LoginUser.objects.create(user_id=user_id, user_pw=user_pw_crypted) # LoginUser 모델에 새로운 object 생성
# 클라이언트한테 내려줄 데이터 정의
data = dict(
user_id=user_id,
user_pw=user_pw_crypted
)
return Response(data=data)
views.py에서는 make_password라는 django의 기본 함수를 사용하여 user_pw를 암호화하고,
암호화한 비밀번호를 보내준다.
이 비밀번호는 복호화가 불가능하다.
그럼 어떻게 인증하냐?
클라이언트에서 올린 user_pw를 동일한 방법으로 암호화 해서 비교함. 즉, 같은 데이터를 암호화해서 같은 데이터가 나옴.
이제 비밀번호를 검증해보자
django에서 제공하는 check_password로 가능
# login/views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from .models import LoginUser
from django.contrib.auth.hashers import make_password, check_password
class AppLogin(APIView) :
def post(self, request) :
user_id = request.data.get('user_id', "")
user_pw = request.data.get('user_id', "")
user = LoginUser.objects.filter(user_id=user_id).first()
if user is None :
return Response(dict(msg="해당 ID의 사용자가 없습니다."))
if check_password(user_pw, user.user_pw) :
return Response(dict(msg="로그인 성공"))
else :
return Response(dict(msg="로그인 실패. 패스워드 불일치!!"))
class RegistUser(APIView) :
...
check_password
첫 번째 인자 : 클라이언트로부터 받은 password
두 번째 인자 : 사용자의 password
첫 번째 인자를 암호화한 것과 두 번째 인자를 비교하여, 같으면 True, 틀리면 False를 반환.
'Study > Django' 카테고리의 다른 글
[Database | ORM] ORM이란 무엇인가 (Django) (0) | 2022.12.02 |
---|---|
[Python | Django] makemigrations 오류 해결 (0) | 2022.11.30 |
[Python | Django] pip install uwsgi Error : Failed buliding wheel for uwsgi (0) | 2022.11.23 |
[Python | Django] 파이썬 장고 설치 (가상환경) (0) | 2022.11.22 |
[Python | 가상환경] 파이썬 가상환경 venv 사용 (0) | 2022.11.22 |