필드 이름을 useremail
에서 email
로 변경
User
가 이미 user에 관한 정보를 담고 있어서, 필드 이름에서 굳이 user를 반복할 필요가 없다.clone 대상인 Instagram에서 회원가입 시 email, password 이외에 성명, 휴대폰 번호 정보를 요구하므로, 필드를 추가
mysql에서 table삭제 후 makemigrations가 적용되지 않을 때 해경 방법
app 폴더 안의 migrations 폴더에서 ~_initial.py
파일을 모두 삭제
mysql 데이터베이스에 접속하여 다음 명령어를 입력
delete from django_migrations where app = '앱 이름'
회원가입 처리에 관한 class view 이름은 일반적으로 register보다 signup이라는 표현이 사용된다.
password 재입력 및 확인 기능은 주로 프론트엔드에서 구현하는 validation(유효성 검사)이므로 삭제해도 된다.
요청을 통해 전달된 email 주소가 기존 데이터베이스의 내용과 중복되는지 확인하는 validations에선 get() 대신 filter().exists()를 사용하는 것이 효율적이다.
filter().exists()
는 조건과 일치하는 데이터가 존재하지 않는 경우, error를 반환하지 않는다. 대신 빈 queryset 객체를 반환하므로, 처리가 편리하다.validation(유효성 검사)에 대한 흐름 처리는 elif 대신 if문을 사용하며, return을 통해 조건에 만족한 경우에 대해 처리하는 것이 일반적이다.
비밀번호 길이 제한을 숫자로 지정하면, 추후 값(비밀번호 길이 제한 숫자)을 바꿀 때 어려울 수 있다. 그러므로 상수를 생성한 뒤 그곳에 데이터를 지정하여 사용하는 것이 좋다.
return 메시지는 클라이언트가 보는 것이 아니고 프론트엔드가 보는 메시지이므로 간단하게 표현하는 것을 권장(메시지 형식 통일시키기 ex) 대문자, 언더바 등)
"Please enter at least 8 characters"
(X)"At least 8 characters"
(O) PASSWORD_LENGTH = 8
if len(password) < PASSWORD_LENGTH:
return JsonResponse({"message":"At least 8 characters"}, status = 400)
if조건문
에 만족하는 경우에 대해 return
으로 흐름 처리를 하면, if조건문
하단에 else문
사용이 필요하지 않다.
이유: return
이 함수가 종료시키기 때문에 else:
없이도, if조건문
에 만족하지 않 경우에 대한 흐름 처리가 가능
elif나 else는 필요하지 않다면 지나친 사용은 자제하자.
KeyError
python
에서 KeyError가 발생하는 이유: 딕셔너리에서 key값이 존재하지 않는 경우
'회원 가입'에서 KeyError가 발생하는 이유: 회원 가입 요청 시 body에 담겨 서버로 전달되어야 할 값이 'key:value'형태로 전달되지 않은 경우
'회원 가입'에서 KeyError를 처리하는 방법: try-except문
으로 처리하며, except KeyError:
로 예외처리를 한다.
** 가독성을 위해 except문은 함수의 가장 하단에 배치하는 것이 좋다.
요청받은 email 데이터와 DB 내부 데이터의 중복 여부를 검사하는 코드를 email 입력 유효성 검사와 비밀번호 입력 유효성 검사 아래로 배치
body 메시지의 key에 해당하는 'name'
과 'phone_number'
에 대해 빈 값(value)이 전달 됐을 경우에 대한 유효성 검사를 추가해야 함
'email'
과 'password'
는 입력 유효성 검사에 의해 error 처리가 이뤄지기 때문에, 빈 값이 입력된 경우에 대한 유효성 검사를 추가할 필요가 없다.bcrypt설치
pip install bcrypt
views.py에서 bcrypt 불러오기
import bcrypt
bcrypt는 string 데이터가 아닌 Bytes 데이터
를 암호화하기 때문에 입력받은 password 데이터를 bytes로 형변환 필요
string 데이터.encode('UTF-8')
bcrypt 라이브러리로 패스워드 암호화하기
bytes type
으로, 모델의 필드 타입이 charfield
인 데이터베이스에 저장하려면 문자열로 형변환을 해줘야 한다. 이를 위해 decode()
가 사용된다. 만약 문자열로 바꾸지 않고 DB에 저장하면, 로그인 처리 시 애를 먹게 되므로 반드시 형변환을 해주자! password = '1234'
hashed_password = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt()).decode()
# 회원 가입 기능 구현 코드
class SignupView(View):
def post(self, request):
data = json.loads(request.body)
PASSWORD_LENGTH = 8
try:
email = data['email']
password = data['password']
name = data['name']
phone_number = data['phone_number']
if name == '' or phone_number == '':
return JsonResponse({'message' : "Empty name or phone_number"}, status =400)
if not ('@' in email) or not ('.' in email ):
return JsonResponse({"message":"Enter a valid useremail"}, status = 400)
if len(password) < PASSWORD_LENGTH:
return JsonResponse({"message":"At least 8 characters"}, status = 400)
if User.objects.filter(email=email).exists():
return JsonResponse({"message":"Duplicate_Useremail"}, status = 400)
hashed_password = bcrypt.hashpw(password.encode('UTF-8'), bcrypt.gensalt()).decode()
User.objects.create(
email = email,
password = hashed_password,
name = name,
phone_number = phone_number,
)
except KeyError:
return JsonResponse({"message":"KEY_ERROR"}, status = 400)
return JsonResponse({"message":"SUCCESS"}, status = 200)
get()
으로 딕셔너리 값 가져오기
딕셔너리 객체.get(key, default값)
KeyError 유효성 검사를 실행하고 싶지 않은 데이터가 있다면, 딕셔너리 객체['key']
대신 딕셔너리 객체.get('key')
를 사용하면 된다. default값을 반환하기 때문에, except문
에 의해 처리되지 않는다.
일치하는 계정이 존재하지 않거나, 비밀번호가 맞지 않을 경우 상태 코드로 401을 반환한다.
client가 잘못된 요청을 보냈음을 믜미
인증되지 않은 사용자가 인증이 필요한 리소스를 요청하는 경우 인증의 필요성을 알려주는 상태 코드. 보통 로그인이 필요한 API를 비 로그인 사용자가 호출했을 때 많이 사용된다.
요청받은 email과 일치하는 사용자의 password(필드값)에 접근하는 코드의 위치는 '사용자 존재 여부를 확인하는 유효성 검사' 다음에 배치
user = User.objects.filter(email=email)
if not user.exists(): # 사용자 존재 여부 확인하는 유효성 검사
return JsonResponse({"message":"INVALID_USER"}, status = 401)
user_password = user.first().password # 존재하는 사용자의 password 데이터에 접근
bcrypt는 Bytes 데이터
를 인자로 받으므로, 로그인 요청 시 입력받은 password 데이터를 bytes로 형변환
password.encode('UTF-8')
로그인 요청자와 일치하는 User정보를 DB에서 가져오기
user = User.objects.filter(email=email)
일치하는 User정보(객체)가 DB에 존재하는 경우, 해당 객체의 password값에 접근한다.
user_password = user.first().password
filter()
로 불러온 queryset객체 내부 요소(class User의 인스턴스 객체)에 접근할 때 주의사항
인덱스를 사용하여 데이터를 가져오는 것은 좋지 않으므로 권장x
user_password = user[0].password
(권장 X)for문
으로 내부 요소값 꺼내기
filter().first()
활용
first()
: queryset 객체의 첫 번째 요소를 가져옴last()
: queryset 객체의 마지막 요소를 가져옴요청 시 입력받은 password와 User객체의 password가 **일치하는지 확인
Bytes 데이터
를 인자로 받는 것을 고려하여 user_password를 bytes로 형변환 if not bcrypt.checkpw(password.encode('utf-8'), user_password.encode('UTF-8')):
return JsonResponse({"message":"INVALID_USER"}, status = 401)
JWT는 사용자의 로그인이 이뤄진 후, 지속해서 매번 로그인할 필요가 없도록 해주는 access token(암호화된 유저 정보)을 생성하여 사용자에게 전달하기 위해 사용된다.
인가(Authorization)도 JWT를 통해 구현될 수 있다.
pyjwt 설치
pip install pyjwt
pyjwt views.py로 불러오기
import jwt
사용자의 로그인이 문제없이 이뤄졌을 경우 access_token 생성
jwt.decode(json 타입의 값, 시크릿 키번호, algorithm='HS256')
발급된 token을 프론트엔드 엔지니어에게 전달
access_token = jwt.encode({'id' : user.first().id}, my_settings.SECRET['secret'], algorithm='HS256')
return JsonResponse({"message":"SUCCESS",'token' : access_token, 'user_email' : user.first().email}, status = 200)
# 로그인 처리 기능 구현 코드
class LoginView(View):
def post(self, request):
data = json.loads(request.body)
try:
email = data['email']
password = data['password']
name = data.get('name')
phone_number = data.get('phone_number')
user = User.objects.filter(email=email)
if not user.exists():
return JsonResponse({"message":"INVALID_USER"}, status = 401)
user_password = user.first().password
if not bcrypt.checkpw(password.encode('utf-8'), user_password.encode('UTF-8')):
return JsonResponse({"message":"INVALID_USER"}, status = 401)
except KeyError:
return JsonResponse({"message":"KEY_ERROR"}, status = 400)
access_token = jwt.encode({'id' : user.first().id}, my_settings.SECRET['secret'], algorithm='HS256')
return JsonResponse({"message":"SUCCESS",'token' : access_token, 'user_email' : user.first().email}, status = 200)
ipconfig getifaddr en0
python manage.py runserver 자신의 IP 주소:8000