그로밋의 개발일기

[DJANGO] 인증과 인가(2) : 비밀번호 암호화 본문

DEV/🟢 DJANGO

[DJANGO] 인증과 인가(2) : 비밀번호 암호화

개발을 하게 되. 2021. 8. 27. 19:49

비밀번호를 그대로 데이터베이스에 저장시 개인정보가 유출되는 문제점이 생긴다. 

비밀번호를 암호화과정이 필요한데, 암호화 과정에 앞서서 살펴봐야할 개념이 있다.

 

바로 인증과 인가 개념이다

물론 코드로 바로 이해하는 것도 좋지만 왜 이 코드를 쓰는지 코드 뒷편에 있는 매커니즘을 이해해하는게 중요하다.

 

인증 Authentication  

웹 사이트에서 인증은 왜 필요할까? 인증이 없으면 사용자 식별이 불가해서회원들이 어떤 것을 하고 있는 추적할 수 없게 된다. 

그렇다면 왜 추적할 수 없는게 문제점일까? 그 이유에는 여러가지 이유가 있겠지만, 사이트운영에 있어 필수적이기때문이다.

트래픽이 몰리는 시간과 어느정도의 트래픽이 발생하는 가를 확인하는 것은 웹사이트 운영에 있어 핵심정보로 이용되기때문에 회원들이 뭘 하고 있는가는 웹사이트 운영의 본질과 이어진다. 

 

결국 인증은 회원가입과 로그인을 말하는데, 인증에 필요한 것은 아이디, 이메일로 말할 수 있는 사용자 개별이름과 비밀번호이다. 

특히 중요한 것은 비밀번호를 어떻게 관리하는 가에 있다. 

최근 해킹 관련 개인정보 유출 이슈가 대두되고, 개인정보보호가  어느 시대보다 중요한 시대가 되가고 있는 시점에서 개인정보를 안전하게 보호조치가 이뤄져야한다. 개인정보보호법에서도 말하고 있듯이 암호화는 필수이다. 

 

그렇다면 비밀번호를 어떻게 관리하면 좋을까?

통신시 개인정보를 주고 받을때 HTTPS 프로토콜을 사용해서 단순히 텍스트형태가 아닌 암호화해서 전송하게 된다. 

(아직 HTTPS 통신과정에 대한 지식은 부족하지만 HTTPS와 HTTP는 보안측면에서 매우 다르다는 것을 알고 넘어가자) 

 

비밀번호 암호화는 어떻게 생성하고 관리를 하게 될까? 

우선 암호에 관한 관점부터 바꾸자. 암호는 뚫리지 말아야한다는 시각에서 벗어나야할 필요가 있다. 암호는 결국엔 시간만 있다면 뚫릴 수 있다고 가정을 하고 암호를 다시 바라보자. 결국 암호화의 의미는 비밀번호를 알 수 없는 값으로 만들어 해커에게 해석할 시간을 길게 준다고 생각하자. 그렇다면 암호를 어떻게 길게 그리고 랜덤하지만 규칙성있게 암호화할 수 있을까? 

이러한 질문에 대해 해쉬함수가 그 답이 될 수 있다.

 

해쉬함수  Hash Function

일방향 해쉬함수는 왼쪽에서 오른쪽으로 가는 것은 쉬워도 오른쪽에서 왼쪽으로 가는 건 쉽지않다. 그 결과값은 모두 1인반해 x의 값이 무수히 늘어나기 때문에 어떤 x가 맞는지 수없이 많은 x를 넣어야 알 수 있다.

이러한 기능을 해주는 것이 일방향 해쉬함수라고 이해하자. 

하지만 여기서도 치명적인 결함이 있는데 모든 x를 넣게 되면 풀리는 것이다. 이러한 보완책으로 saltingkeystretching이 나왔다. 

 

Salting & Keystretching 

말그대로 소금뿌리듯 입력한 비밀번호와 임의로 생성한 문자열(salt)를 합쳐서 해시값을 저장하는 방법이다. 
여기서 해커가 무작위 대입을 통해 해시값을 계산하는데 필요한 시간을 대폭 늘리기 위해 salting 및 해싱을 여러번 반복해서 원본값을 유추하기 어렵게 만드는 것이 keystretching이다. 

(참고자료: https://www.wpsecurity.press/how-secure-is-the-wordpress-passwords-algorithm/)

비밀번호 생성은 쉽게 말해 비밀번호 + 솔트값을 묶어 해쉬값을 처리하고

비밀번호 조회는 사용자가 입력한 비밀번호와 솔트값을 묶어 해쉬값으로 처리한 데이터와 저장되어 있는 비밀번호가 같은지 아닌지를 판단하는 것이다. 

 

이러한 salting과 keystretching을 적용하기 위한 대표적인 라이브러리가 있는데,  bcrypt 가 그것이다. 

bcrypt를 이용하게 되면,hash 결과값에 salt, keystretching을 같이 보관하기 때문에 DB설계를 복잡하게 할 필요가 없다고한다. 

비밀번호를 구조화해서 보여준다. 
해쉬함수로 표현된 암호를 표현한다면?

 


인가 Certification  

토큰이 있는 사용자만 무사히 로그인할 수가 있다! 

 

사용자가 서버에 로그인하면 해당 사용자가 맞는지 확인하는 과정과 + 토큰 발급 (입장권)까지가 인가이다 

 

여기서 핵심은 🔥인증된 사용자에게 토큰을 발급한다는 것이다. 

http 특징은 request - response (요청-응답) 이 statelss한 성질이 있다.  말그대로 state 성질이 없다는 것이다.

각각의 통신은 독립적이라 로그인에 성공해도 토큰을 주지 않으면 로그인 본질적인 기능이 없어진다. 

 

이제 서버관점에서 생각해보자                                                                                                                                                       

서버가 사용자가 로그인 할 경우 로그인했다는 것을 사실을 어떻게 알 수 있을까? 

headers에 메타데이터를 보내서 확인한다. 이러한 메타데이터를 JSON Web Token ( JWT ) 라고 한다.

인가 Flow<<
요청 1:  서버에 로그인 
요청 1 응답:  1 에서 200 OK와  Token  발행

요청 2: 사이트에 접속시도, 발행받은 Token 과 함께 요청을 보내게 된다.
요청 2 응답: 접속 OK

 

JSON Web Token 

헤더에는 토큰 타입과 해시 알고리즘 정보가 들어간다. 

헤더의 내용은 BASE64방식으로 인코딩(암호화가 아님!!) 해서 JWT의 가장 첫부분에 기록된다.

누구나 원본을 받아볼 수 있어 개인정보는 절대 담아선 안된다.

 

{ "alg" : "HS256", "typ":"JWT" } 

마지막 서명은 JWT가 원본그대로라는 것을 확인할때 사용한다. 진짜 토큰인지 복사된 토큰인지 확인하기 위함이다. 

또한 헤더와 내용 그리고 JWT secret(별도생성)을 헤더에 지정된 암호 알고리즘으로 암호화해서 전송한다고 한다. 복호화라고 함

 

프론트엔드가 JWT를 전송 TO 백엔드API 서버

전송받은 JWT의 서명부분을 복호화 -> 서버에서 생성한 JWT가 맞는지 확인 

 

JWT는 우리 사이트에서 발급했다는 증명서.

서버에 사용자에 대한 정보를 저장하면 서버가 저장해야 할 데이터가 많아짐으로 사용자에게 이러한 데이터를 갖고있게 해주는 것

인가의 과정에서 사용자의 신원을 확인하고 해당 요청이 유효하도록 허가증을 내주는 것이다. 

 

 

이제 이를 활용해서 사이트 회원가입 시 비밀번호를 암호화하는 코드를 구현하자

우선 라이브러리부터 설치하자. 

pip install bcrypt 
pip install pyjwt
import json
import re 
import bcrypt
import jwt

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

from users.models import User
from my_settings import SECRET_KEY

# Create your views here.
class Signup(View):
    def post(self, request):        
        try: 
            data = json.loads(request.body)
            
            name          = data['name']
            phone_number  = data['phone_number']
            gender        = data['gender']
            address       = data['address']
            birth         = data['birth']
            email         = data['email']
            password      = data['password']

            if not re.match('^\d{3}-\d{3,4}-\d{4}$', phone_number):
                return JsonResponse({"MESSAGE": "INVALID_PHONE_NUMBER"}, status=400)

            if not re.match('^[a-zA-Z0-9+-_.]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$', email):
                return JsonResponse({"MESSAGE": "INVALID_EMAIL"}, status=400)

            if len(password) < 8:
                return JsonResponse({"MESSAGE":"INVALID_PASSWORD"}, status=400)
           
            if User.objects.filter(email = email).exists():
                return JsonResponse({"MESSAGE":"EMAIL_EXIST"})
            
            User.objects.create(
                name          = data['name'],
                phone_number  = data['phone_number'],
                gender        = data['gender'],
                address       = data['address'],
                birth         = data['birth'],
                email         = data['email'],
                =====================================
                password      =  ~~~~~~~~~
                =====================================
            )
            return JsonResponse({"MESSAGE":"SUCCESS"}, status=201) 
            
        except KeyError:
            JsonResponse({"MESSAGE":"KEY_ERROR"}, status=400)
            
        except ValueError:
            JsonResponse({"MESSAGE":"VALUE_ERROR"}, status=400)

 

비밀번호 암호화 형식 

bcrypt. hashpw( 입력된 암호 인코딩(to byte), 솔트값추가).decode('utf-8') 

password = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt()).decode('utf-8')

🔥 마지막 decode를 넣어서 비트값이 아닌 값으로 넣어놔야 나중에 인가에서 사용할 수 있는 데이터가 된다. 

'코딩 > 🟢 DJANGO' 카테고리의 다른 글

[API] REST API  (0) 2021.09.12
[DJANGO] 인증과 인가 (3) : 로그인 구현  (0) 2021.08.27
[DJANGO] 인증과 인가(1) : 회원가입 구현하기  (0) 2021.08.26
[DJANGO] CRUD: POST & GET 로직  (0) 2021.08.22
[DJANGO] CRUD  (0) 2021.08.20