인증과 인가는 API에서 가장 자주 구현되는 기능중 하나
Private and Public API 둘다 기본적인 인증과 인가를 요구
유저의 identification을 확인하는 절차, (유저의 아이디와 비번을 확인하는 절차)
로그인 절차:
1. 유저 아이디와 비번 생성
2. 유저 비번 암호화 해서 DB에 저장.
3. 유저 로그인 -> 아이디와 비밀번호 입력
4. 유저가 입력한 비밀번호 암호화 한후 암호화되서 DB에 저정된 유저 비밀번호와 비교.
5. 일치하면 로그인 성공
6. 로그인 성공하면 access token
을 클라이언트에게 전송.
7. 유저는 로그인 성공후 다음부터는 access token
을 첨부해서 request를 서버에 전송함으로서 매번 로그인 해도 되지 않도록 한다.
유저의 비밀번호는 절대 비밀번호 그대로 DB에 저장 하지 않는다.
유저의 비밀번호는 꼭 암호화 해서 저장 해야 한다.
비밀번호 암호에는 단방향 해쉬 함수(one-way hash function)가 일반적으로 쓰인다.
다이제스트(digest)
를 생성한다. 원본 메시지를 알면 암호화된 메시지를 구하기는 쉽지만 암호화된 메시지로는 원본 메시지를 구할 수 없어서 단방향성(one-way)
이라고 한다.0b47c69b1033498d5f33f5f7d97bb6a3126134751629f4d0185c115db44c094e
값이 나온다.d34b32af5c7bc7f54153e2fdddf251550e7011e846b465e64207e8ccda4c1aeb
값이 나온다. 실제 비밀번호는 비슷하지만 해쉬 함수 값은 완전히 틀린것을 볼 수 있다. 이러한 효과를 avalance라고 하는데 비밀번호 해쉬 값을 해킹을 어렵게 만드는 하나의 요소이다.Salting
Key Stretching
단방향 해쉬값을 계산 한 후 그 해쉬값을 또 또 해쉬 하고, 또 이를 반복하는 것을 말한다.
앞서 언급했듯이 유저가 로그인에 성공한 후에는 access token이라고 하는 암호화된 유저 정보를 첨부해서 request를 보내게 된다.
access token
을 통해 해당 유저 정보를 얻을 수 있음으로 해당 유저가 가지고 있는 권한(permission)도 확인 할 수 있다.access token
을 생성한다. access token
에는 유저 정보를 확인할 수 있는 정보가 들어가 있어야 한다 (예를 들어 user id).access token
을 첨부해서 보낸다.access token
을 복호화 한다.pip install bcrypt
import bcrypt
bcrypt는 str
데이터가 아닌 Bytes
데이터를 암호화 합니다.
따라서 암호화시에 bytes화 해야합니다.
파이썬에서는 str
을 encode하면 bytes(이진화) 되고, Bytes
를 decode하면 str
화 합니다.
encode, decode시에는 우리가 인식할 수 있는 형태로 변환하기 위해 'UTF-8' 유니코드 문자 규격을 사용합니다.
본격적으로 bcrypt 라이브러리를 사용하겠습니다.
먼저 패스워드를 암호화 해보겠습니다.
암호화된 데이터의 타입은 무엇일까요? 당연히 Bytes 입니다!
암호화된 데이터의 타입은 무엇일까요? 당연히 Bytes 입니다!
bcrypt.checkpw() 메소드가 바로 그 일을 하고 있습니다.
이 메소드에는 규칙이 있습니다. (입력받은 패스워드, 저장된 암호화된 패스워드)
둘 다 데이터 타입은 Bytes
여야 합니다.
jwt또한 라이브러리를 사용해서 구현
먼저 PyJWT 라이브러리를 설치
pip install pyjwt
import jwt #패키지명은 pyjwt이지만 임포트할때의 이름은 jwt입니다.
SECRET = 'secret' #'랜덤한 조합의 키' 예제이므로 단순하게 'secret'이라고 하겠습니다.
access_token = jwt.encode({'id' : 1}, SECRET, algorithm = 'HS256')
print(access_token)
b'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6MX0.-xXA0iKB4mVNvWLYFtt2xNiYkFpObF54J9lj2RwduAI'
위와 같이 라이브러리를 동작시킬 수 있습니다.
jwt의 결과물도 bytes
타입 이네요.
그럼 이제 인증을 위한 매개체를 만들었습니다. 바로 jwt token 일명 access_token입니다.
이렇게 발급된 토큰은 어떻게 사용할 수 있을까요? 바로 프론트엔드 엔지니어에게 전달해야 합니다.
그렇다면 로그인이 성공하면 위와 같은 방식으로 발행한 토큰을 전달하면 되겠죠!
그렇다면 이제 인증을 위한 기본 요건이 달성 되었으니, 인증을 통과한 사용자만 접근하려면 토큰을 받아서 다시 우리가 발행한 토큰이 맞는지 확인 해야겠죠?
header = jwt.decode(access_token, SECRET, algorithm = 'HS256')
print(header)
{'id': 1}
decode 결과는 바로 우리가 encode 할 때 넘겼던 header값인 { 'id' : 1}입니다.
이 말은 우리가 담아 넘기는 header 값을 통해 유저를 식별할 수 있다는 이야기입니다.
인증하는 코드는 어디에 구현해야 할까요?
오늘 까지 한 것.
회원가입 : email validation 추가됨
로그인 : 비밀번호 암호화 및 토큰 부여 가능
import json
import bcrypt
import jwt
import re
from django.views import View
from django.http import JsonResponse, HttpResponse
from project_westagram.settings import SECRET_KEY
from .models import Account
# 치킨먹고싶다...
class SignUpView(View):
def post(self, request): #포스트로 받을 시 저장
data = json.loads(request.body)
signupdb = Account.objects.all() #유저 테이블정보 다 가져와
try:
user = Account(
name = data['name'],
phone_number = data['phone_number'],
email = data['email'],
password = data['password'])
email_valid = re.compile('^[a-zA-Z0-9+-_.]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$')
if email_valid.match(data['email']) == None:
return JsonResponse({"message" : "email_error"}, status = 400)
elif len(data['password']) < 8 :
return JsonResponse({"message" : "short_password"}, status = 400)
elif Account.objects.filter(email = data['email']).exists() or Account.objects.filter(name = data['name']).exists() or Account.objects.filter(phone_number = data['phone_number']).exists() :
return JsonResponse({"message" : "duplicated_information"}, status = 401)
else:
Account.objects.create(
name = data['name'],
phone_number = data['phone_number'],
email = data['email'],
password = bcrypt.hashpw(data['password'].encode("UTF-8"), bcrypt.gensalt()).decode("UTF-8")).save()
return JsonResponse({"message" : "SUCCESS"}, status = 201)
except KeyError:
return JsonResponse({"message" : "KEY_ERROR"}, status = 400)
def get(self, request): #겟으로 받을시 유저테이블에 있는것 출력
account_data = Account.objects.values()
return JsonResponse({'Account Data' : list(account_data)}, status = 200)
class LoginView(View):
def post(self, request):
data = json.loads(request.body)
user_password = data['password'] #헷갈릴까바 안썼음
try:
if 'name' not in data and 'email' not in data and 'phone_number' not in data:
return JsonResponse({'message' : 'KEY_ERROR'}, status = 400)
elif 'password' not in data:
return JsonResponse({'message' : 'KEY_ERROR'}, status = 400)
elif Account.objects.filter(email = data['email']).exists():
exist_user = Account.objects.get(email = data['email'])
if bcrypt.checkpw(data['password'].encode('UTF-8'), exist_user.password.encode('UTF-8')):
token = jwt.encode({'user_id' : exist_user.id}, SECRET_KEY, algorithm='HS256').decode('UTF-8')
header = jwt.decode(token, SECRET_KEY, algorithm = 'HS256')
print(header)
return JsonResponse({'token' : token}, status = 200)
return JsonResponse({"message" : "INVALID_USER"}, status = 401)
return JsonResponse({"message" : "No_Exist_User"}, status = 401)
except KeyError:
return JsonResponse({'message' : 'KEY_ERROR'}, status = 400)
def get(self, request): #get메쏘드로 회원가입된 데이터들을 list형태로 반환
account_data = Account.objects.values()
return JsonResponse({'Login' : list(account_data)}, status = 200)
```