인증과 인가, bcrypt와 jwt

Psj·2020년 11월 10일
1

bcrypt

테스트를하기위해서 auth라는 가상환경을 새로 설치하고 그 가상환경에
pip install bcrypt 를 하여 bcrypt를 설치하였다

그리고 python을 열고 import bcrypt를 하였다.

password = '1234' <-password변수에 문자열로 된 '1234'를 넣는다.

이제 비밀번호를 해쉬해주는 해쉬패스워드함수를 실행하기로한다.
그리고 레인보우테이블 공격을 막기위해 salt값을 추가해서 hash값을 조금더 강인하게 만든다.

bcrypt 프로젝트 홈페이지에서 gensalt함수 확인


드래그 한 부분이 hash에 salt를 하는 gensalt이다.

gensalt를 하여 hash화를 시도하니 유니코드 객체는 hashing전에 반드시 인코딩되어야한다고 타입에러를 반환하고있다.

encoding과 decoding

파이썬에서 문자열은 유니코드로 함으로 인코딩한다는 의미는 유니코드를 utf-8 또는 아스키형식의 bytes코드로 변환하는것을 의미한다.

즉 문자열 '1234' str타입을 인코딩하면 bytes 1234로 인코딩이 되게된다.
반대로 bytes 1234를 디코딩하면 문자열 '1234'가 된다.

문자열 '1234'를 담은 password를 utf-8 형식으로 인코딩하는것이다.
인코딩한 값을 encoded_password변수에 넣어 값을 반환하니
b'1234' 가 나왔다.


인코딩된 변수인 encoded_password의 타입을 확인하니 bytes 인것을 확인할수있다.

인코딩 되기전의 password변수는 타입이 str인것을 확인할수있다.

간단히 표현하면 bytes로 인코딩된값은 b'1234'이고 디코딩된값은 '1234'로
인코딩을하면 앞에 b가 붙고 디코딩된값은 b가 빠지는것으로 쉽게 확인할수있다.

이번에는 인코딩한 값을 디코딩을 시켜보았다.

인코딩된 값이 들어있는 encoded_password를 이용하여 이미지와같이 입력하여 디코딩하면 디코딩된 값이 들어있는 decoded_password변수의 값이 '1234' str타입으로 바뀐것을 확인할수있다.

이젠 맨처음에 정한 password = '1234'(str타입의 '1234')를 맨처음과같은 에러가 발생하지않게 인코딩한후 gensalst를 이용한 hashing을 진행하겠다.


위 이미지와같이 정상적으로 hashing이 된것을 확인할수있다.

salt값


salt값은 이렇게 새로 입력할때마다 랜덤으로 바뀌는것을 확인할수 있다.

그렇다면 salt값이 들어간 hash된 패스워드는 이후에 어떻게 비교할수있을까?

만약 패스워드를 '1234'로 만들어 이 '1234'에 salt값을 넣어 hashing하였다면 이 값에 들어간 salt값은 그대로 저장되어 고정된다.
그러므로 나중에 비밀번호 확인을 할 수 있는것이다.


salt값의 자리

hash된 패스워드의 salt값은 이렇게 앞자리에 오게된다.



hash된 값의 타입

타입이 bytes인것을 확인할수있다.

주의해야할점
데이터베이스의 유저테이블에 패스워드를 저장할때 '1234'와같은 평문 문자열을 저장하는것이 아닌 hash화 된 값을 디코딩(b가 빠진값)하여 저장해야한다!!

bcrypt check password함수를 알아보겠다.
check password함수는 그 이름이 의미하듯이 우리가 로그인 엔드포인트를 구현할때 패스워드를 비교할때 사용된다.

bcrypt홈페이지에서 check password함수(checkpw) 확인

첫번째 인자로는 입력한 패스워드의 인코딩된 값이 들어가고 두번째 인자로는 hash된 패스워드의 인코딩된 값이 들어간다.

우리는 데이터베이스에 디코딩된 hash값을 저장했기때문에 문자열인데
chech password함수에는 인코딩하여 bytes화 해서 넣어줘야한다.


이렇게 첫번째 인자에는 '기존의 비밀번호'.encode('utf-8')을 넣고
두번째 인자에는 인코딩된 hash된 패스워드를 넣는다.
일치하므로 True가 나온것을 확인할수있다.

이렇게 '123'이라는 맞지않는 비밀번호를 넣으면 False가 나오는것을 확인할수있다.

JWT(JSON Web Tokens)

bcrypt는 단방향 hash알고리즘이라 salting과 stretching을 하면 복호화가 거의 불가능에 가깝지만 JWT(JSON Web Tokens)는 복호화하면 암호화해서 담긴 정보를 확인할수 있어서 사용자가 로그인하면 백엔드서버가 JWT토큰을 프론트엔드에 발행하게 되고 프론트엔드는 API를 요청할때 header의 Authentication에 이 access token을 넣어서 보내면 백엔드서버는 이 access token에 담겨있는 header, payload, signature를 확인해서 인가방법을 제공하게된다.

즉 http특성상(이 블로그의 "http에대하여" 참고) stateless한 특성(stateless : server side에 client와 server의 동작, 상태정보를 저장하지 않는 형태, server의 응답이 client와의 세션 상태와 독립적임)이 있어서 이전에 로그인한 사용자의 상태를 알수가없다.
하지만 로그인시에 발행한 JWT토큰을 사용하게되면 백엔드서버에서는 이 토큰정보를 해석하는 데코레이터를 만들어서 인가가 필요한 엔드포인트들을 사용할수있다.

JWT(JSON Web Tokens) 생성과 사용

JWT를 사용하기 위해선
pip install pyjwt 명령어로 설치를 먼저 하여야 한다.

그리고 import jwt를 하고 사용한다.

jwt 공식홈페이지 사용법

encode 메소드로 jwt를 만들수 있는것을 확인할수 있다. -> jwt.encode

첫번째 인자는 payload에 담을 정보를 써주고
두번째 인자는 secret키인데 실제 사용할때는 위 이미지의 예제와 같이 단순히 secret이라는 문자열을 사용하면 안되고 코드에 secret이라고 적어서 github에 올리면 이 키와 세번째 인자인 HS256 알고리즘을 사용해서 payload에 담긴 정보를 복호화 할수 있기때문에 절대 이 키는 노출되면 안되고 암호화 알고리즘 방식또한 절대 노출되면 안된다.


우리는 첫번째 인자에
백엔드 데이터베이스에서 유저정보를 가져와서 그 id를 넣어주는것처럼 테스트해보겠다. 즉 id가 5인 유저가 로그인 했다고 가정을 하고 테스트를 해보겠다.

두번째 인자로는 'secret'을 넣는다
(원래는 좀더 복잡한 키를 써야하지만 지금은 공식문서와같이 secret으로 하였다)

세번째 인자인 algorithm은 공식문서와 마찬가지로 'HS256'으로 하였다.

이것이 id가 5번인 유저를 jwt인코딩하는것이다.

주의해야할점
payload에 유저의 패스워드나 이메일등 개인정보를 절대 담아서는 안된다는것이다. 하지만 유저 아이디는 노출되어도 단순한 숫자이기때문에 누군가 의도적으로 데이터베이스에 접근하지않는이상 의미없는 숫자라고 할수있기때문에 안전하다.


여기서 인코딩된 값을 보면 중간중간에 . 으로 구분되어 있는데
제일 앞부분이 header
두번째 부분이 payload
마지막 부분이 signature 이다.
이렇게 세부분으로 구성되어있다.


이 값의 형식은 bytes인것을 확인할수있다.

이 값을 로그인한후에 리턴해주려면 디코딩해서 문자열로 리턴을 해주어야한다.


이제 프론트엔드에서 header의 Authentication에 jwt를 넣어서 호출한다고 가정하고 발행된 web tokes를 디코딩해보도록 하겠다.

jwt.decode 메소드를 이용하고
함수의 첫번째 인자에는 디코딩할값을 넣어주고
두번째 인자와 세번째 인자에는 기존의 토큰과 동일한 secret키와 알고리즘을 입력해준다.

그러면 위 이미지처럼 {'user-id':5}가 복호화 된것을 확인할수 있다.

profile
Software Developer

0개의 댓글