[Django] API와 Login기능 만들기

12 분 소요

들어가기 전에

유튜버가 말하길 아마 서버 개발자로 회사에 들어간다면 가장 많이 할 업무 중에 하나라고 한다. 앱에 새로운 기능을 추가한다거나, 홈페이지에 새로운 데이터를 보여주는 기능을 추가하거나 할 때 항상 서버에서 데이터를 내려줘야 하기 때문이다.

하지만 회사마다 구조가 다르고 환경이 다르기 때문에, 여기서 하는 것과 다를 수 있다. 운 좋게 Django를 하는 곳에 가서 Django Rest Framework를 사용해서 API를 만든다면 운이 좋은 거고, 어디는 Spring을 사용할 수 도 있고, 어디는 Ruby on Rails를 쓸 수도 있다. 따라서 언어를 기반으로 배운다기보다는 전체적인 흐름이 어떻게 돌아가는지를 중점적으로 배우는걸 추천했다.

먼저 어떤 목적으로 기능을 추가해야 하는 건지 기획단계부터, 클라이언트와 어떤 방식으로 통신할 건지, 서버에서는 어떤 테이블의 어떤 데이터를 내려줘야 하는지 등 여러 가지 상황이나 업무 프로세스의 흐름 등을 보면 좋을 것 같다. 가장 중요한 것은 “왜?”이다. 일은 하는데 “이걸 왜 만들어야 하지?”라는 물음은 항상 필요하다. 대기업이든 스타트업이든. 자기가 어떤 일을 하는지 의미를 부여하는 것만큼 중요한 건 없다고 생각한다. 진행되는 일이 어떤 건지 고민을 하다 보면 프로덕트를 좀 더 이해하게 되고, 사용자 관점에서 생각하게 되며 더 효율적인 방법을 찾을 수 있다고했다.

여기서 말하는 API(Application Process Interface) 대한 정의가 상당히 애매한데, 회사마다 부르는 말도 다르다고 한다. 기본적인 정의는 클라이언트가 서버에 호출해서 데이터를 수정하거나 조회하는 작업을 말한다. 기본적인 개념은 RPC(Remote Procedure Call)인데, 이걸 RPC라고 부르는 데는 거의 없더라고요. 대부분 API라고 부르거나 어디는 TR(Transaction)이라고 부릅니다. 어떻게 부르든 클라이언트에서 필요로 하는 작업을 해준다는 것은 변함없기 때문에 개념만 알아두시면 될 것 같다.

REST를 사용하기 위해 DRF 설치하기

Django 프레임워크에서 REST API를 사용하기 위해서 django-restframework라는 패키지를 설치한다. 이걸 설치해야만 API를 만들 수 있는 건 아니지만 보다 쉽게 만들 수 있게 도와주는 패키지라고 생각하시면 된다.

pip install djangorestframework
  • 아래 공홈 링크
  1. https://www.django-rest-framework.org/

  2. Home - Django REST framework www.django-rest-framework.org

보통 원티드나 구인 사이트에서 서버 개발자 필요 스펙을 보면, Django 서버를 사용할 경우 항상 같이 따라오는 게 있는데 바로 “DRF“이다. 왜 굳이 약자로 쓰는지는 모르겠는데.. Django Rest Framework의 약자라고 한다. 즉 Django로 서버를 사용하는 회사는 대부분 DRF를 사용한다는 거다.

기초적인 DRF 사용법

[ dJango로 restful API 서버만들기 1] - django 서버 생성소스코드 GIT : https://github.com/tkdlek11112/django_restful 강의영상 YouTube restfulAPI 서버란? API 서버인데 일종의 규칙을 적용하여 사용하기 편리하게 만든것! (자세한것은 구글링이나 영상 참조 ><) 개..cholol.tistory.com

DRF를 설치하면 이제 DRF에서 제공하는 편리한 기능들을 사용해서 빠르게 API를 만들 수 있다.

그전에 mysql 연동하기

이전에 포스팅에서 docker로 mysql을 띄우는 예제가 있었는데, 시작하기에 앞서 mysql과 django를 제대로 연결시키고 시작하겠다. API를 만드는 데 있어 DB가 중요하기 때문이다.

docker run -d -it --rm --name mysql-db -p 3306:3306 -e MYSQL_ROOT_PASSWORD=admin123! -v /home/ubuntu/mysql/data:/var/lib/mysql -e MYSQL_PASSWORD=admin123! mysql:5.7 --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci

docker가 설치되어 있다면 위 스크립트를 실행하면 자동으로 mysql이 실행된다.

MYSQL_ROOT_PASSWORD=에 여러분이 설정할 패스워드를 입력하시면 된다.

위 명령어를. sh파일로 저장해 노면 나중에 복붙 하지 않고도 쉽게 실행할 수 있다.

imgmysql-docker.sh로 만들면 나중에 쉽게 실행할 수 있음

vi mysql-docker.sh로 파일을 만든 다음 위 명령어를 복사해놓기만 하면 된다.

단 실행파일로 설정되어있지 않기 때문에 그냥 실행하려면 sh mysql-docker.sh로 실행해야 하고, 그냥 파일 이름만 입력했을 때 실행되게 하려면 chmod를 살짝 바꿔준다.

imgchmod +x [파일이름] 을 하면 실행파일로 변한다.

위처럼 녹색으로 변하거나 파일 이름 뒤에 *가 붙으면 실행파일로 변했다는 뜻이다. (리눅스 버전마다 표기방법이 다름)

이제 그냥 mysql-docker.sh를 실행하면 docker가 실행된다.

이제 실제로 mysql이 실행되었는지 datagrip툴을 이용해서 열어보자.

DB 접속하는 툴은 Golden, orange, toad, mysql-workbench 등등이 있는데 입맛대로 쓰시면 된다.

DB에 접속하기 전에 AWS console에 들어가서 3306 포트에 대한 방화벽을 확인해줘야 한다. 내부에서 접속할 때는 상관없긴 한데, 외부에서 mysql에 접속하기 위해서는 3306 포트에 대한 방화벽을 해지해야 한다.

img역시 안되있다.

imgMYSQL/Aurora를 선택하면 3306포트로 선택된다. 그냥 TCP 3306해도 된다.

이제 방화벽도 풀었기 때문에 DB TOOL에서 접속할 수 있다.

imgHOST, ID, PASSWORD를 입력하면 끝 HOST는 AWS 주소 입력하면된다.

서버 정보를 입력하고 기본 계정은 root로 되어있다. root password는 아까 docker 실행할 때 옵션으로 주었던 password를 입력하면 된다.

img연결 완료

연결되면 기본적인 mysql 시스템 테이블들만 보인다.

아직 Django에서 사용되는 DB가 없는 상태인데, Django setting.py에서 DB설정을 해주고 migrate를 한번 해줘야 테이블들이 생성된다.

# settings.py

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'HOST': 'choaws.duckdns.org',
        'NAME': 'server_dev',
        'USER': 'root',
        'PASSWORD': 'admin123!',
        'PORT': '3306',
        'OPTIONS': {'charset': 'utf8mb4'},
    }
}

settings.py에 DB 설정을 보면 아마 sqlite3으로 되어있을거다. default로 sqlite3을 사용하는데 이걸 mysql로 바꿔주고 기타 정보들을 입력해주면 된다. NAME의 경우 migrate를 하고 나서 생성되는 DB Schema의 이름이기 때문에 취향에 맞게 설정해주시면 된다.

전부 설정하고 로컬에서 실행을 시켜보면..

imgmysqlclient 설치?

친절하게도 mysqlclient를 설치하라고 알려준다. 만약 conda 가상 환경을 사용하시는 분이라면 이 메시지가 뜨지 않을 거다. 왜냐면 conda에는 이미 설치가 되어있어서 ㅎㅎ 그냥 venv 쓰시는 분들은 에러가 뜨는데, pip를 이용해 mysqlclient라는 패키지를 다운로드하면 된다.

img호락호락하게 설치 안됨

하지만 pip install mysqlclient 하면 위와 같은 에러가 나올거다. 윈도우 XX#@%SD

구글링 해주면 따로 다운로드하여서 설치하라고 한다.

https://www.lfd.uci.edu/~gohlke/pythonlibs/#mysqlclient

위 url로 들어가서 mysqlclient 본인의 PC 사양에 맞게 다운로드하면 된다.

필자는 mysqlclient‑1.4.6‑cp38‑cp38‑win_amd64.whl 이걸 다운로드하였다. (python3.8, amd64이기 때문!)

다운로드한 파일을 프로젝트에 옮기고 신나게 명령어를 쳤는데…?!

img

문서를 좀 읽어보니까 whl설치는 pip가 최신 버전이어야 한다고 한다

그래서 pip를 20.2.2 버전으로 올렸다.

img파이참 설정에서 20.2.2를 선택해서 다운받을 수 있다.

이제 20.2.2로 올리고 나서 명령어를 다시 치니까 설치 완료!!

img

img이제 실행해보면 server_dev 찾을 수 없음

이제 위 에러가 나올 텐데, 당연히 나오는 에러!!

settings.py에 선언했던 server_dev DB가 아직 안 만들어져서 나오는 에러인데 일단 mysql에 server_dev라는 DB를 만들어주고, migrate를 진행해야 한다.

imgserver_dev schema 생성

일단 datagrip에서 schema 생성해주고..

imgmakemigrations + migrate

migrate 하면…

img기본 테이블 생성

Django 기본 테이블들이 만들어진다. 이젠 앱 실행 가능!!

img앱 실행 완료

LOGIN API 만들어보기

사실 LOGIN이란 API가 간단하게 만들면 한없이 간단하고, 복잡하게 만들면 한없이 복잡해지는 서비스라고 한다.

일단 가장 간단한 버전으로 ID와 PASSWORD를 입력하면 서버에 저장된 ID, PASSWORD와 비교해서 성공/실패를 내려주는 API를 만들어 보자.

Django에는 기본적으로 admin 사용자를 위한 사용자 테이블이 제공된다. 일단은 이 테이블을 사용하지 않고, 새로운 Model을 만들어서 실습해보겠다. 일단 앱을 만들자

imgstartapp login

login이라는 이름으로 app을 하나 만든다. 이렇게 만들면 프로젝트 안에 login이라는 폴더가 자동으로 생성된다.

imglogin 폴더 안에 여러가지 .py파일이 생긴다.

우선 models.py에서 사용자의 ID와 PW를 저장할 모델 하나를 만들어준다.

다른 건 필요 없고 그냥 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바이트인 user_id와 user_pw필드를 만들었다. 이제 makemigrations와 migrate를 해주면 DB에 반영된다. migrate를 하기 전에 settings.py에서 INSTALLED_APP에 login을 추가해야 한다.

#server_dev/settings.py
...
INSTALLED_APPS = [
    ...
    'rest_framework',
    'login'
]
...

이제 migrate를 해보자.

imgmakemigrations + migrate

img테이블이 생김!

테이블을 보면 우리가 만든 user_id, user_pw 말고도 id필드가 생겨있는 걸 볼 수 있는데, 장고 모델은 default로 pk값으로 사용하는 id 필드를 추가하기 때문에 생긴 필드이다. 그냥 필드가 생길 때마다 1씩 자동으로 증가하는 index라고 생각하면 된다.

이제 모델은 만들었고 여기에 id와 pw를 생성하는 api를 먼저 만들어보자.

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 모델에 새로운 object를 만들게 한다. 이제 특정 url을 통해 RegistUser를 호출할 수 있도록 urls.py을 설정한다.

기본적으로 startapp을 통해 django app을 만들면 urls.py 파일은 생성되지 않는다. 그래서 new file로 urls.py를 만들어준다.

# login/urls.py
from django.conf.urls import url
from . import views


urlpatterns = [
    url('regist_user', views.RegistUser.as_view(), name='regist_user'),
]
# server_dev/urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('login/', include('login.urls')),
]

login/urls.py에 ~/regist_user를 호출하면 RegistUser를 호출하도록 설정하고, root urls.py에는 include를 통해 login앱에 있는 urls.py파일을 링크시킵니다.

이제 ~/login/regist_user를 통해 POST로 요청을 날리면 DB에 데이터를 쌓을 수 있다.

imgInsomnia나 postman을 통해 호출해보자.

필자는 Insomnia라는 툴을 자주 쓰는데 보통 Postman이라는 툴을 많이 쓴다고한다. 어떤 거든 상관없이 POST를 날려봅시다. 이번 프로젝트는 user_id에 mychew를 넣고, user_pw에 password123! 을 넣었다.

imgDB도 확인 완료

아주 간단하게 API를 호출해서 사용자의 ID PW를 저장하는 CALL을 만들었는데요, 아직 문제점이 많아 보입니다. 일단 툴을 사용해서 여러 번 호출해보자.

img호출할 때마다 mychew라는 user_id 생성

사용자의 id는 중복되는 값 없이 유니크해야 하는데, 호출할 때마다 mychew라는 user_id가 생성되었다.

클라이언트는 현재 서버에 어떤 값들이 저장되어있는지 모르기 때문에 중복되는 값이 있는지 체크하는 로직이 서버에 필요하다. 뿐만 아니라 ID값이 정상적인 포맷인지 검증도 해야 하는데요, 서버에서 허용하는 문자가 맞는지와 같이 여러 가지 제약사항들을 검증하는 로직이 필요하다.

서버 개발자의 역할 중 제일 중요한 역할이 바로 이런 데이터/로직 검증이다. 사실 INPUT을 받아서 OUTPUT을 주면 되는 간단한 로직은 정말 쉽다. 하지만 회사에서 어떤 서비스를 하냐에 따라 업무적인 로직(Business Logic이라고 흔히 부릅니다)이 들어갈 수밖에 없는데, 서버 개발자는 이 로직을 꿰고 있어야 한다.

간단한 로그인 API를 만들기에 있어서도 사용자의 ID를 어디까지 허용할 건지, 잘못된 값이 들어오면 어떤 응답을 클라이언트에 전달할 건지, 비밀번호를 암호화해서 저장할 건지 등 정해야 할 것이 많다. 이것들을 결정하고 적용하는 것 역시 서버 개발자의 몫이다. (물론 큰 조직은 업무 로직을 결정하는 팀이 따로 있다. 하지만 적용은 개발자의 몫~)

일단 동일한 user_id값을 사용하지 못하도록 로직을 설정해보자

# 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_pw = request.data.get('user_pw', "")

        if LoginUser.objects.filter(user_id=user_id).exists():
            data = dict(
                msg="이미 존재하는 아이디입니다."
            )
            return Response(data)

        LoginUser.objects.create(user_id=user_id, user_pw=user_pw)

        data = dict(
            user_id=user_id,
            user_pw=user_pw
        )

        return Response(data=data)

간단하게 if문을 통해서 입력된 user_id값에 해당하는 사용자가 있는지 검사하는 로직을 추가했다. 이전과 같이 mychew라는 아이디를 올려보겠다.

img이미 존재하는 아이디

이제 중복된 아이디 생성은 피할 수 있겠다. 하지만 코드상에서 예외처리를 한다고 해도 DB에 같은 값이 들어갈지도 모른다. Model 선언 부분에서 user_id 필드에 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 = '로그인 테스트 테이블'

중복처리 외에도 특수문자 불가능, 숫자로 시작 불가능, 길이 10바이트 이상 등 여러 가지 제약조건 등을 설정해주어야 할지도 모릅니다. 물론 클라이언트에서 데이터에 대한 검증을 하고 서버로 전달하겠지만, 서버 개발자는 클라이언트를 믿지 않는다 -_-++ (종특).

사용자 id만큼 중요한 게 있는데 바로 pw다. 이런 개인정보가 될 수 있는 정보들은 암호화를 해서 DB에 저장해야 한다. 특히 password 같은 경우는 서버 개발자도 모르게 단방향으로 암호화해서 저장해야 한다. 단방향과 양방향의 차이는, 단방향의 경우 암호화할 경우 어떻게 해서도 복호화가 불가능한다. 따라서 서버 개발자도 특정 사용자의 비밀번호를 알 수 없다. 만약 알 수 있다면 큰일 날수도?

회사마다 각자 암호화하는 방식이 다르긴 한데 django model field encrypt라고 검색하니까 django용 암호화 패키지가 있다. 그거를 사용해 보자.

https://pypi.org/project/django-searchable-encrypted-fields/

django-searchable-encrypted-fieldsDjango model fields encrypted using Pycryptodome AES-256 GCM.pypi.org

설치는 간단하게 pip

pip install django-searchable-encrypted-fields

그리고 settings.py에 아래 추가

# server_dev/settings.py
INSTALLED_APPS += ["encrypted_fields"]

FIELD_ENCRYPTION_KEYS = [
    "f164ec6bd6fbc4aef5647abc15199da0f9badcc1d2127bde2087ae0d794a9a0b"
]

이제 models.py에서 user_pw를 암호화 필드로 바꿔보자.

# login/models.py
from django.db import models
from encrypted_fields import fields

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)
    user_pw = fields.EncryptedCharField(max_length=20, null=False, default=False)
    class Meta:
        db_table = 'login_user'
        verbose_name = '로그인 테스트 테이블'
# login/views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from .models import LoginUser
from encrypted_fields import fields


class RegistUser(APIView):
    def post(self, request):
        user_id = request.data.get('user_id', "")
        user_pw = request.data.get('user_pw', "")

        if LoginUser.objects.filter(user_id=user_id).exists():
            # 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)


        data = dict(
            user_id=user_id,
            user_pw=user_pw
        )

        return Response(data=data)

응답을 좀 정확하게 보기 위해서 views.py도 손봤다. models.py를 수정했기 때문에 migrate를 다시 해주어야 한다. 혹시 모르니까 DB에 데이터를 다 지우고 makemigrations와 migrate를 진행한다.

진행하고 나서 아까와 같이 user_id와 user_pw를 올려보겠다.

img일단 성공

일단 아까와 응답은 똑같은데… DB를 한번보자.

img

DB에 헥사 값으로 저장이 되어있다…

뭐 누구나 패키지를 만들 수 있긴 하지만.. 이 패키지는 못쓸 것 같네요.

뭐 일단 암호화는 제대로 되었다. 하지만 이 암호화 방법은 비밀번호를 암호화 하기에는 적절하지 않다.

왜냐하면…

img동일한 아이디를 올릴경우 DB에 있는 값 조회함

아까 views.py를 조금 수정해서 동일한 user_id를 올리 경우 DB에 있는 값을 그대로 내려주도록 수정했다. 근데 DB에 있는 값을 그대로 내려준다면 암호화된 user_pw를 내려주어야 하는데 복호화되어 내려오는 것을 확인할 수 있다. 일반적인 개인정보 같은 경우 이렇게 되어도 상관없지만, 비밀번호는 단방향 암호화여야 한다. 따라서 복호화가 불가능해야 하기 때문에 이 암호화 방법은 사용할 수 없을거 같다. 대신 생년월일이나 핸드폰 번호와 같은 개인정보를 암호화하는 경우는 사용 가능하다.

그럼 비밀번호는 어떻게 암호화할까?

사실 Django에서 제공하는 user테이블을 사용하면 기본적으로 password가 암호화 필드로 먹혀있고 자동으로 단방향 암호화가 되기 때문에 기본 user모델을 사용하는 것도 하나의 방법이다. 하지만 이번 프로젝트는 따로 테이블을 만들었기 때문에 user모델과 비슷한 기능을 하도록 user_pw필드를 바꿔보자. 사실 filed를 바꾸는 건 아니고 데이터를 모델에 집어넣을 때 다른 암호화 로직을 이용해 데이터를 바꾼다.

# login/models.py
from django.db import models
from encrypted_fields import fields


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)
    # user_pw = fields.EncryptedCharField(max_length=20, null=False, default=False)
    class Meta:
        db_table = 'login_user'
        verbose_name = '로그인 테스트 테이블'
# 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_pw = request.data.get('user_pw', "")
        user_pw_crypted = make_password(user_pw)    # 암호화

        if LoginUser.objects.filter(user_id=user_id).exists():
            # 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)

        data = dict(
            user_id=user_id,
            user_pw=user_pw_crypted
        )

        return Response(data=data)

models.py에서 user_pw 필드를 다시 charfield로 바꾸고 길이를 조금 늘려줍니다. 암호화하면 보통 길이가 길어지기 때문에 넉넉하게 늘려준다. views.py에서는 make_password라는 django 기본 함수를 사용해 user_pw를 암호화한다. 이제 아까와 같이 user_id와 user_pw를 올려보겠다.

img암호화~!

암호화된게 보이시나요? DB 역시 암호화된 값으로 user_pw가 들어가 있는 것을 확인할 수 있다. 이 값은 절대 복호화할 수 없는 값이기 때문에 사용자의 비밀번호를 아무도 알 수 없다.

그럼 여기서 궁금증이 생기 실 텐데요. 아무도 비밀번호를 모른다면 어떻게 비밀번호를 체크해서 로그인을 시킬까?

방법은 생각보다 간단한다. 클라이언트에서 올린 user_pw를 동일한 방법으로 암호화해서 암호화된 값을 비교하는 방식으로 사용자를 검증할 수 있다. 복호화가 불가능할 뿐이지 같은 데이터를 암호화하면 항상 같은 데이터가 나오기 때문이죠. 따라서 사용자의 비밀번호가 어떤 값인지는 모르지만, 클라이언트에서 올라온 user_pw를 동일한 방법으로 암호화한 결괏값이 DB에 저장된 user_pw값과 같다면, 클라이언트에서 올라온 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_pw', "")
        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): 
    ...
# login/urls.py
from django.conf.urls import url
from . import views


urlpatterns = [
    url('regist_user', views.RegistUser.as_view(), name='regist_user'),
    url('app_login', views.AppLogin.as_view(), name='app_login'),
]

check_password를 사용해보기 위해 AppLogin이라는 API를 새로 만든다. 실제로 클라이언트에서 올라온 user_id와 user_pw를 검증해서 로그인을 시켜주는 API이다.

AppLogin 내부를 보면 클라이언트에서 올라온 user_id를 가진 사용자가 있는지 검색하고, 있을 경우 그 사용자의 user_pw가 클라이언트에서 올라온 user_pw와 일치하는지 check_password라는 함수를 통해 검증하고 있다. check_password는 비교하는 두 값이 같으면 True, 다르면 False를 리턴한다. 따라서 ~/login/app_login이라는 url로 user_id와 user_pw를 호출하면 아래와 같이 결과값이 출력된다.

img성공

img실패

make_password와 check_password를 이용해 단방향 암호화 비밀번호를 구현했다. 물론 이 방법 말고도 암호화 방법은 매우 많다. 이 방법 역시 서버 개발자와 업무 로직 담당자가 결정해야 할 몫입니다

마치며

이번 포스팅에서는 Django에서 REST API를 만들기 위해 DRF를 설치해보고, 간단하게 LOGIN API를 만들어보았다. 실제로 API를 만들 때 서버 개발자 입장에서 고려해야 할 부분이 상당히 많다. 단순하게 DB에 있는 데이터를 내려주는 거는 상관없는데 특정 로직이 필요한 API의 경우 데이터 검증이나 예외처리 등등 파생되는 업무들이 많아진다.

규모가 큰 서비스의 경우 데이터가 산재되어 있어 필요한 데이터를 한 번에 내려주는 것 역시 번거로운 작업이 된다. 여러 테이블에 나뉘어 저 있는 데이터를 어떤 key를 사용해서 조회하고 어떤 방식으로 내려줄지 결정하는 것 역시 서버 개발자의 역할인데, 이걸 잘하는 서버개발자 일 잘하는 서버 개발자라고 할 수있다.

이번에는 간단하게 LOGIN에서 발생할 수 있는 예외/제약사항들을 살펴봤는데 위에 언급한 것 말고도 상당히 많은 제약사항들이 존재한다.

기본적으로 LOGIN에서 빼놓을 수 없는 것이 보안과 세션 유지인데, JWT나 OAUTH2.0과 같은 이름의 보안 기술…. 이 것들 중 어떤 것을 어떤 방식으로 적용할지 역시 서버 개발자의 몫이기 때문에 생각보다 서버 개발자가 할 일이 많다.

보안 같은 경우는 이번 시리즈에서 다룰지 안 다룰지 모르겠습니다만, 간단하게 이론 정도는 짚고 넘어가는 방식으로 진행할 예정입니다.

다음 포스팅에서는 이번 포스팅에 이어서 LOGIN 관련 API 및 추가적인 API를 설계할 때 어떻게 접근해야 하는지에 대해 다루도록 하겠다.

댓글남기기