인증 & 인가

인증(Authentication)

Authentication은 유저의 identification을 확인하는 절차

일반적 절차

  • 유저 아이디와 비번 생성 -> 유저 비번 암호화 해서 DB에 저장 > 유저 로그인 -> 아이디와 비밀번호 입력 > 유저가 입력한 비밀번호 암호화 한후 암호화되서 DB에 저정된 유저 비밀번호와 비교 -> 일치하면 로그인 성공 > 로그인 성공하면 access token을 클라이언트에게 전송 -> 유저는 로그인 성공후 다음부터는 access token을 첨부해서 request를 서버에 전송함으로서 매번 로그인 해도 되지 않도록 한다.

Login View 파일에 작성

토큰 발행 & 암호화

실습 준비

  1. 라이브러리 설치

bcrypt , JWT 라이브러리를 설치한다.

> pip install bcrypt
> pip install pyjwt
  1. import
python manage.py shell 
> import bcrypt
  1. bcrypt 암호화 방법

bcrypt를 사용하게 되면 str 데이터가 아닌 Bytes 데이터로 암호화가 진행됨. 꼭 Bytes로 변환을 해줘야함. 파이썬에서는 str 을 encode하면 bytes(이진화) 되고, Bytes 를 decode하면 str 화 할 수 있음. encode, decode시에는 우리가 인식할 수 있는 형태로 변환하기 위해 'UTF-8' 유니코드 문자 규격을 사용

  1. bcrpyt.hashpw() - 오류 확인하기
>>> bcrypt.hashpw()
Traceback (most recent call last):
  File "<console>", line 1, in <module>
TypeError: hashpw() missing 2 required positional arguments: 'password' and 'salt'
>>>

오류 확인. 2개의 인자를 필요로 하는데, password와 salt를 필요로한다.

  1. 먼저 소금을 생성하기
>>> salt = bcrypt.gensalt()
>>> print(salt)
b'$2b$12$fuDkhOEKVo7BVpl8SXW7aO'
>>>

이런 식으로 소금값?을 구해줄 수 있다. 아직 암호는 건드리지 않았다.

  1. password 설정
>>> bcrypt.hashpw('12341234', salt)
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/Users/dongheon/miniconda3/envs/CRUD2/lib/python3.8/site-packages/bcrypt/__init__.py", line 80, in hashpw
    raise TypeError("Unicode-objects must be encoded before hashing")
TypeError: Unicode-objects must be encoded before hashing

다시 오류 발생. 잃어 보면 Unicode-objects must be encoded before hashing이라는 뜻은 일단 해싱을 하려면 인코드를 해야한다는 뜻.

>>> password = '12341234'.encode('utf-8')
>>> print(password)
b'12341234'
>>> type(password)
<class 'bytes'>
>>>

타입도 바뀌어 있고, 프린트로 출력을 해보면 앞에 b가 붙어 bytes 형태로 인코드 되어 있는 것을 확인할 수 있음

  1. 정상 작동 확인
>>> hashed_password = bcrypt.hashpw(password, salt)
>>> print(hashed_password)
b'$2b$12$fuDkhOEKVo7BVpl8SXW7aOB/JBLjHHUCnT8MAXFFbbXjPjqrz6fZK'
  1. checkpw
>>> bcrypt.checkpw(password, hashed_password)
True

checkpw 통헤 확인

이렇게 암호화된 방식은 일방향 암호화 즉 복호화 할 수 없도록 암호화하는 방식

  1. Decode('utf-8')
else:
	User.objects.create(
		email       = data['email'],
		name        = data['name'],
		phoneNumber = data['phoneNumber'],
		password    = hashed_pw.decode('utf-8')

회원가입 views.py 작성 시 주의 사항.

password 부분을 hashed_pw로 바꿔주고 나서, .decode('utf-8') 형태로 다시 작성해 줘야 한다. .decode('utf-8')로 그대로 나둘 시 바이트 형식으로 계속 남아 있게 되어 로그인 시 비교를 할 수 없게 된다.

코드 리뷰 & 토큰 발행

import bcrypt
import jwt
import json

from django.http  import JsonResponse
from django.views import View 
from .models      import User 

class UserView(View):
    def post(self, request):
        data = json.loads(request.body)
        PASSWORD_LENGTH = 8
        
        email       = data['email']
        name        = data['name']
        phoneNumber = data['phoneNumber']
        password    = data['password']
        
        encoded_pw = password.encode('utf-8')
        hashed_pw = bcrypt.hashpw(encoded_pw, bcrypt.gensalt())
        
        if not (email or password):
            return JsonResponse({"message" : "KEY_ERROR."}, status = 400)
        
        try:
            if User.objects.filter(email = data['email']).exists() == True:
                return JsonResponse({"message" : "같은 아이디가 존재합니다."}, status = 401)
        except:
            pass
        
        if ('@' and '.') not in email:
            return JsonResponse({"message" : "아이디를 제대로 써주세요"}, status = 401)

        if len(password) < PASSWORD_LENGTH:
            return JsonResponse({"message" : "비밀번호를 8글자 이상으로 늘려주세요"}, status = 401)

        else:
            User.objects.create(
                    email       = data['email'],
                    name        = data['name'],
                    phoneNumber = data['phoneNumber'],
                    password    = hashed_pw.decode('utf-8')
            )
            return JsonResponse({"message" : "Success"}, status = 200)
        

class SignInView(View):
    def post(self, request):
        data = json.loads(request.body)
        
        email = data['email'] 
        password = data['password']          
        
        try:
            if User.objects.filter(email=data['email']).exists():
                user_email = User.objects.get(email=data['email']) 
                if bcrypt.checkpw(data['password'].encode('utf-8'),user_email.password.encode('utf-8')):
                    access_token = jwt.encode({'id': user_email.id}, 'secret', algorithm='HS256')
                    return JsonResponse({"token": access_token, "message": "SUCCESS"}, status=200)
                else:
                    return JsonResponse({"message": "INVALID_PASSWORD"}, status=401)
            else:
                return JsonResponse({"message": "INVALID_EMAIL"}, status=401)
            
        except KeyError:
            return JsonResponse({"message": "KEY_ERROR"}, status=400)

중요하다고 생각하는 부분은 짚고 넘어가기!

try ~ except 구문

try:
    if User.objects.filter(email = data['email']).exists() == True:
        return JsonResponse({"message" : "같은 아이디가 존재합니다."}, status = 401)
except:
    pass

try ~ except 구문을 잘 이해하지 못해 코드 중간에 껴 넣은 형태로 짜버렸음. 추후 다시 고쳐야 하는 부분.

password 부분 상수 처리

PASSWORD_LENGTH = 8
        
if len(password) < PASSWORD_LENGTH:
		return JsonResponse({"message" : "비밀번호를 8글자 이상으로 늘려주세요"}, status = 401)

처음에는 if 안에 직접적으로 숫자를 넣어 일정 글자 수 이상을 조건으로 주었었음. 하지만 그렇게 하면 기획 시 제시하는 기준이 바뀌게 되었을 때 다시 찾아서 글자 수를 지정해줘야함. 이렇게 바뀔 수 있는 부분은 따로 변수를 만들어 관리를 해주면 좋을 것.

토큰 관련

if bcrypt.checkpw(data['password'].encode('utf-8'),user_email.password.encode('utf-8')):
access_token = jwt.encode({'id': user_email.id}, 'secret', algorithm='HS256')

access_token = jwt.encode({'id': user_email.id}, 'secret', algorithm='HS256')

access_token을 발행을 할건데, 일단 jwt라는 라이브러리?를 사용하여 진행함. 인자로 3개가 필요한데, 첫 번째는 유저의 아이디 값을 id라는 키에 저장을 해야하고, secret 키를 적어주고, 마지막엔 어떤 알고리즘으로 인코드할 것인가에 대해 적어줌.

좋은 웹페이지 즐겨찾기