TIL_27 | [Django] 삐빅 -. 이거... 인증된건가? 인증인가!

code_sign·2021년 2월 1일
1

django

목록 보기
3/4

우리는 수많은 웹사이트 속에 있고,
웹사이트 수 만큼 존재하는 각자의 ID가 있다.

웹에서는 어떻게 ID, PW를 보관하고 비교하고 암호화할 수 있을까??

비밀번호 암호화 (Bcrypt)

Bcrypt??

위키에 따르면 bcrypt는 비밀번호 해시함수다. (Hash형 자료구조 참고)

Niels Provos와 David Mazieres에 의해 만들어졌으며 Blowfish라는 암호에 기반하였다.

  • Bcrypt는 조정할 수 있는 해시알고리즘을 써서 패스워드를 저장
  • Bcrypt는 패스워드를 해싱할 때 내부적으로 랜덤한 솔트를 생성
  • 때문에 같은 문자열에 대해서 다른 인코드된 결과를 반환
  • 공통된 점: 매번 길이가 60인 String을 만든다.

적용해보기!

일단 우리는 터미널에서 해당 입력어를 통해 install한다!

pip install bcrypt                                       

encode: 암호화

그런 다음, import bcrypt를 해주고, 비밀번호를 암호화를 설정해준다!
암호화하는 방법은 의외로 간단하다!

bcrypt.hashpw(문자열.encode('utf-8'), bcrypt.gensalt())

위와 같은 코드로 작성하면 되는데, 받는 인자는 다음과 같다.

  • A string (byptes)
  • Salt : bcrypt.gensalt()를 통해 자동으로 랜덤값을 받을 수 있다.

실제 프로젝트에 적용해보면,

import json

from django.views import View
from django.http  import JsonResponse
import bcrypt

from .models import Accounts


class AccountView(View):
    def post(self, request):
        data = json.loads(request.body)
        
        # 필수사항 미입력시
        try:
            email        = data['email']
            password     = bcrypt.hashpw(data['password'].encode('utf-8'), bcrypt.gensalt())
            .
            .
            .
mysql> select password from accounts;
+-----------------------------------------------------------------+
| password                                                        |
+-----------------------------------------------------------------+
| 12345678                                                        |
| b'$2b$12$TqRaIL6suby0BQHBVfNL6udGCgBxc0F3Uz5KtsUkqaHlT6xJZlxFy' |
+-----------------------------------------------------------------+
2 rows in set (0.00 sec)

이와같은 결과가 나온다.

mysql에도 예전에는 '12345678'로 들어갔던 암호가 알수없는 문자열로 들어간걸 볼 수 있다! (위대한 암호화!!)

decode: 복호화

이건 더 쉽다!(너무 쉽다 쉬워!!)

bcryptcheckpw()를 쓰면 된다. 이 함수는 2개의 인자를 갖는데,

  • A string (byptes)
  • 비교할 암호(Bytes 형)

으로 비교해주면 쉽게 결과가 나온다.

# password 있는지 확인
if 'password' in data:
  password = bcrypt.checkpw(data['password'].encode('utf-8'), account.password)
else:
  return JsonResponse({'message': 'KEY_ERROR'}, status=400)
[01/Feb/2021 07:12:10] "POST /account/login HTTP/1.1" 200 22

잘 연결이 되는것을 확인 할 수 있다!

그럼 Token으로 인증은?!😂

이번에 쓸 라이브러리는 pyjwt!

JWT? 존맛탱... 그런건가??

JWT는 JSON Web Token라는 약어로 정보를 안전하게 전송하기 위해 정의된 공개된 표준(RFC 7519)이다. 이 라이브러리는 아래와 같은 특징을 가지고 있다.

  1. JSON 객체를 사용하여 자가수용적이다.
    : 자가수용적(self-contained)이란 JWT는 필요한 모든 정보를 가지고 있다라는 뜻이다. 헤더 정보와 전달할 데이터, 검증할 수 있는 서명 데이터(signature)를 포함하고 있다.
  2. 신뢰할 수 있다.
    : 디지털 서명에 의해 검증할 수 있고 신뢰할 수 있다. 공개키, 개인키 쌍으로 서명될 수 있다.
  3. 수많은 프로그래밍 언어에서 지원된다.
    : JWT는 C, Java, Python, C++, R, C#, PHP, JavaScript, Ruby, Go, Swift 등 대부분의 주류 프로그래밍 언어에서 지원된다.

왜 쓸까?

  • 회원 인증: Token을 넘기기 때문에 stateless 같은 속성이 상쇄된다.
  • 데이터 용량: JWT는 기존의 XML 방식보다 덜 복잡하고 인코딩 된 사이즈가 작다. 그래서 HTTP와 HTML환경에서 쓰기 좋다!
  • 사용성: 대부분의 언어를 지원하니 XML을 사용하는 SAML보다 만들기 쉽다.

구조

  1. 헤더(Header): 헤더는 타입(JWT)과 알고리즘(HS256 같은)을 담는다.
  2. 내용(payload): 보통 유저 정보와 만료기간이 객체형으로 담긴다.
  3. 서명(signature): header, payload를 인코딩 한 값을 합친뒤 SECRET_KEY로 해쉬한다.

JTW 실습해보기🧑🏻‍💻

pip install pyjwt

해당 JWT를 다운받아준다. (python이라서 pyjwt)

import jwt # 라이브러리 이름은 pyjwt지만 import는 jwt로 사용!

class LoginView(View):
    def post(self, request):
    
        data = json.loads(request.body)
        access_token = data.get('token')
    
        if access_token:
            payload = jwt.decode(access_token, my_settings.SECRET, algorithms=my_settings.ALGORITHM)
        
            if Accounts.objects.filter(email=payload['email']):
                print('-------------------------- token!!')
                return JsonResponse({'message': 'SUCCESS', 'token': access_token, 'info': payload}, status=200)
        try:
            ...
            access_token = jwt.encode({'email' : account.email}, my_settings.SECRET, algorithm=my_settings.ALGORITHM)
    
            return JsonResponse({'message': 'SUCCESS', 'token': access_token}, status=200)

        except KeyError:
            return JsonResponse({'message': 'KEY_ERROR'}, status=400)
    

importjwt로 해주면 된다.

encode!

먼저, try안에 있는 access_token이라는 변수를 보면 jwt.encode()가 실행되었다.
해당 코드의 jwt.encode()에는 3가지 인자를 받아서 사용했다.

  • 토큰에 담을 정보(유저의 id 등)
    -> primary한 'email'를 사용
  • 랜덤한 문자열
    -> project 만들때 발행받은 SECRET_KEY를 my_settings.py에 담아 사용
  • signature을 만들때 사용할 알고리즘 종류
    -> 특정한 알고리즘을 하나 정해 my_settings.py에 담아 사용

해당 인자를 바탕으로 생성하게 되면 salt가 추가되어 잘 양념된 JWT값이 나오게 된다!

HTTP/1.1 200 OK
Content-Length: 162
Content-Type: application/json
Date: Tue, 02 Feb 2021 01:20:02 GMT
Referrer-Policy: same-origin
Server: WSGIServer/0.2 CPython/3.8.5
Vary: Origin
X-Content-Type-Options: nosniff
X-Frame-Options: DENY

{
    "message": "SUCCESS",
    "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6InRqZHduczAzMDlAbmF2ZXIuY28ua3IifQ.2m-qLIVJ__TT1pEnUjEkOSOvptcbozg6MvKIvRFIZBQ"
}

decode

try문 밖에 있는 if문들을 살펴보자

첫번째 if에서는 token이라는 키가 있어 값이 있다면 해당 token이 우리가 발행한게 맞는지 검사하는 decode()를 실행하게 된다.

그뒤 payload 변수를 만들어 jwt.decode()를 실행하게 된다.
jwt.decode()도 3가지의 인자를 가지게 된다.

  • 전달받은 token
  • encode() 할 때 사용되었던 동일한 SECTER_KEY
  • encode() 할 때 사용되었던 동일한 ALGORITHMS

request.body에 발급받은 토큰의 정보를 보내주면 아래와 같이 잘 실행되어 'email'의 정보를 받아올 수 있다.
(id의 종류로는 3개가 있지만 지금의 경우엔 'email'을 사용했다.)

http POST 127.0.0.1:8000/account/login email="abc@naver.com" 
password=12345678 
token='eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6InRqZHduczAzMDlAbmF2ZXIuY28ua3IifQ.2m-qLIVJ__TT1pEnUjEkOSOvptcbozg6MvKIvRFIZBQ'
{
    "info": {
        "email": "abc@naver.com"
    },
    "message": "SUCCESS",
    "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6InRqZHduczAzMDlAbmF2ZXIuY28ua3IifQ.2m-qLIVJ__TT1pEnUjEkOSOvptcbozg6MvKIvRFIZBQ"
}

Today, Learned

배운점

  • Bcrypt 종류를 처음 알게 되었다. (비밀키, 공개키만 알고 있었다.)
  • 해싱 함수를 통한 암호화
  • JWT의 특징들과 쓰게되는 이유
  • JWT가 가지는 각 부분들에 대한 정보
  • JWTencode(), decode()의 사용 방법
  • project 만들때 왜 SCERET_KEY를 주고, 왜 그것을 git에 올리면 안되는지ㅋㅋㅋㅋ

느낀점

  • 해싱함수... hash 동작 방법을 알지만, 어렵게 느껴진다.. 만드신분들 감사합니다...ㅎㅎ
  • JWT가 다양한 언어에서 쓰이는 것을 보고 또 우리의 편의성을 위해 다른분들이 고생했다는 것을 절실히 깨닫는 오늘의 공부..

오늘의 한마디

발전하는 암호화기술, 그것을 부수려 발전하는 해킹기술... 그리고 그뒤를 맹렬히(?) 따라가는 나!!

profile
방탈출 좋아하는 코딩덕후

2개의 댓글

comment-user-thumbnail
2021년 2월 2일

오 성준님 이번 글 많이 도움됐어요. 감사합니당.

답글 달기
comment-user-thumbnail
2021년 2월 2일

oh 오 oh 오

답글 달기