회원가입 및 비밀번호 암호화 프로세스
- request body를 통해 유저가 가입 시 입력한 비밀번호를 받아서 encoding
- bcrypt로 비밀번호 hashing 및 salting
- hasing + salting한 비밀번호를 decoding해서 DB에 저장
1. request body를 통해 유저가 입력한 비밀번호를 받아서 encoding
유저가 ‘user1234’라는 비밀번호로 가입을 했다고 가정하자. hashing과 salting을 해주기 위해 문자열에서 byte 타입으로 변환해준다. 이 과정을 인코딩이라 한다.
password = 'user1234'
encoded_password = password.encode('utf-8')
print(encoded_password)
로 출력한 인코딩 결과값은 이렇게 나온다.
b'user1234'
2. bcrypt로 비밀번호 hashing 및 salting
hashing은 단방향 암호화 방식이다. 같은 값을 해싱하면 해싱값 또한 항상 같다. 따라서 해싱값 데이터가 쌓이면 역으로 암호가 드러나는 위험성이 있다. 이 점을 보완하기 위해 salting 과정을 추가해서 더 복잡하게 암호화 한다.
import bcrypt
hashed_password = bcrypt.hashpw(encoded_password, bcrypt.gensalt())
print(hashed_password)
로 출력한 인코딩 결과값은 이렇게 나온다. 암호화에 성공했다.
b'$2b$12$PS77USI4OnMydAuPoPCoK.9rCCOghSmMrR82Xn6L3x2xRYaeA7i8m'
3. hasing + salting한 비밀번호를 decoding해서 DB에 저장
DB에 비밀번호를 저장할 때 반드시 암호화한 형태를 저장해야 한다. 2번에서 마지막 결과값을 보면 byte 타입으로 인코딩 되어있음을 알 수 있는데, DB에 저장할 때는 다시 string 타입으로 디코딩 해줘야 한다.
decoded_hashed_password = hash_password.decode('utf-8')
print(decoded_hashed_password)
로 출력한 인코딩 결과값은 이렇게 나온다.
$2b$12$Q.bIZfHuJPgPICdJbcO8r.6xHBhXuhNXYSuSfQuOQXPV/c.5hF38K
이것을 DB에 저장하는 것까지 회원가입 프로세스다. 이제 유저가 로그인 시 입력한 비밀번호와 DB에 저장된 암호화된 비밀번호 일치 여부를 어떻게 확인하는지 살펴보자.
로그인 시 인증 프로세스
- request body를 통해 유저가 로그인 시 입력한 아이디 또는 비밀번호를 받아서 DB 테이블에서 유저 정보 찾기
- bcrypt로 입력 받은 비밀번호와 DB 테이블에 있는 암호화 비밀번호가 일치하는지 확인
로그인 유저 인가 프로세스
- 로그인 성공 시 토큰 부여
실제 구현
기존에 만들어둔 회원가입, 로그인 코드의 views.py를 아래와 같이 수정했다.
import json
from django.views import View
from django.http import JsonResponse, HttpResponse
from .models import Users
import bcrypt
import jwt
class MainView(View):
def get(self, request):
return JsonResponse({'Welcome to':'Westagram', 'Sign-up':'/users/sign-up', 'Log-in':'/users/log-in'}, status=200)
class SignUpView(View):
def post(self, request):
# 가입 시 유저가 입력한 아이디, 비밀번호 값을 request body를 통해 받는다.
data = json.loads(request.body)
# 유저가 입력한 비밀번호값 data['password']을 byte 타입으로 인코딩 한 후
# bcrypt를 이용해 hasing과 salting을 해준다.
hased_pw = bcrypt.hashpw(data['password'].encode('utf-8'), bcrypt.gensalt())
# 해싱한 암호를 다시 디코딩 해서 DB에 저장 가능한 형태로 만들어준다.
decoded_hashed_pw = hased_pw.decode('utf-8')
try:
if data['username'] == '':
return JsonResponse({'message':'ID_IS_REQUIRED'}, status=401)
elif data['password'] == '':
return JsonResponse({'message':'PASSWORD_IS_REQUIRED'}, status=401)
elif Users.objects.filter(username=data['username']).exists():
return JsonResponse({'message':'ID_EXISTS'}, status=409)
# 유저가 입력한 아이디가 기존 아이디와 겹치지 않는다면 if문 안 코드를 실행한다.
elif not Users.objects.filter(username=data['username']).exists():
# 유저가 입력한 아이디는 DB에 그대로 저장해주고, 비밀번호는 위에서 암호화 한 형태로 저장한다.
Users(
username = data['username'],
password = decoded_hashed_pw
).save()
return JsonResponse({'message':'WELCOME'}, status=200)
except:
return JsonResponse({'message':'INVALID_ID'}, status=401)
# get 메서드 생략
class LogInView(View):
def post(self, request):
# 로그인 시 유저가 입력한 아이디, 비밀번호 값을 request body를 통해 받는다.
data = json.loads(request.body)
try:
# 로그인 시 입력한 아이디 값 data['username']에 맞는 유저 정보가 DB에 존재한다면 if문 안 코드를 실행한다.
if Users.objects.filter(username=data['username']).exists():
# 객체 형태로 바로 리턴되는 get을 이용해 유저가 입력한 아이디 값에 해당하는 객체를 구해준다.
user_id = Users.objects.get(username=data['username'])
# 유저가 입력한 비밀번호와 DB에 저장된 비밀번호가 일치하는지 비교해준다.
# 비교는 둘 다 byte 타입인 상태에서 해야한다.
if bcrypt.checkpw(data['password'].encode('utf-8'), user_id.password.encode('utf-8')) == True:
# user_id 객체의 PK값인 id를 특정 알고리즘을 사용해 암호화하여 토큰을 생성한다.
# 여기서의 id는 유저 아이디가 아니라 DB 테이블 상 고유값 id다.
access_token = jwt.encode({'id':user_id.id}, 'secret', algorithm='HS256')
# 정상적으로 동작하면 토큰과 함께 status code 200을 리턴한다.
return JsonResponse({'token': access_token.decode('utf-8')}, status=200)
else:
return JsonResponse({'message':'비밀번호가 틀립니다.'}, status=401)
else:
return JsonResponse({'message':'아이디가 없습니다.'}, status=401)
except:
return JsonResponse({'message':'INVALID_USER'}, status=401)
# get 메서드 생략
어려웠던 점
처음에는 인증, 인가 과정이 views에서 일어나는지 models에서 일어나는지조차 판단하기 어려웠다. 이번 주 코드카타 짝 덕분에 아래와 같이 인증 프로세스를 정리했고, 테이블을 보니 수정/추가해야 할 코드가 명확해졌다.
유저 정보가 든 데이터 테이블 구조 자체를 바꿀 일이 없으므로 작업은 오로지 views.py에서만 진행하면 된다.