Authentication / Authorization

LILO Ghim·2021년 11월 21일
0

인증(Authentication)

user의 identification을 확인하는 절차(user의 id와 password를 확인)

네트워크를 거쳐서 비밀번호가 전달 될 때, 네트워크를 통해 data가 노출 될 수 있기 때문에 https로 보안을 강화를 하게 되는데 이때, API service는 client가 입력한 값을 특정 algorithm으로 hash하여 database에 저장한다.

salt + key stretching

이 때, 단방향 hash를 보완하고자 salting, key stretching을 실행함

사용자가 login에 성공하면 server는 token을 보내준다.(단방향 해쉬의 경우,)하나의 알고리즘만 있다면 매번 입력할 때마다 같은 해쉬값만을 가지는데 이를 보완하기 위해 hashing하기 전에 salting으로 랜덤값을 붙여준다.
결과적으로 hash값은 다르게 나오지만 나중에 비교를 하기 위해 salt값도 저장을 해준다.

- salting

실제 비밀번호 이외에 추가적으로 random salt값을 붙여서 hashing하기 때문에 같은 비밀번호 '1234'를 입력해도 다 다른 결과가 나올 수 있다.

- key stretching

hashing을 여러 번 반복하는 것

- bcrypt

암호를 hashing 해주는 라이브러리. salting과 key stretching을 구현한 hash 함수로 bcrypt는 처음부터 비밀번호를 단방향 암호화 하기 위해 만들어진 함수이다.


bcrypt 함수 사용


pip install bcrypt

import bcrypt

password = '1234'

# 비밀번호를 hash 해주는 함수 실행
bcrypt.hashpw(password, bcrypt.gensalt())

# 이 때 error가 발생하는데, unicode 객체는 hashing전에 반드시 인코딩 되어야 함. 
# 즉, 문자열 password를 encoding(string -> byte)해서 hash한다

# encoded_password -> b'1234'
encoded_password = password.encode('utf-8')

type(encoded_password)
<class 'bytes'>

----------------------------------------------------------------------------
# decoding (byte타입의 password를  -> string으로 변환함)

decoded_password = encoded_password.decode('utf-8')
decoded_password
'1234'

----------------------------------------------------------------------------
# encoding된 password를 hashing

hashed_password = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt())
hashed_password
b'$2b$12$UqyCUvf7/iVMzbjhZlhReO6h6dcckryK62G9/3gBtuRhFwZ5V0s6i'

----------------------------------------------------------------------------
# salting

bcrypt.gensalt()
b'$2b$12$N1N8ULTQpTRFH7OAUMB4xO'

bcrypt.gensalt()
b'$2b$12$8d/anqqd6hr9po76KyAIaO'

# salt 함수를 호출할 때마다 salt 값이 랜덤으로 생성되는데, 해쉬된 값을 어떻게 비교할 수 있을까?
# 해쉬된 패스워드에 salt 값이 추가되어 있음

salt = bcrypt.gensalt()
salt
b'$2b$12$8d/anqqd6hr9po76KyAIaO'

hashed_password = bcrypt.hashpw(password.encode('utf-8'), salt)

hashed_password
b'$2b$12$C/WQWqP2KNU9t1jm7JhrHOf28uKkk1MeTrt1MBC5QEycp1YpdDTEa'

# hashed_password는 byte 형태이므로 str로 decoding 해서 user table에 저장한다
type(hashed_password)
<class 'bytes'>

- bcrypt checkpw (비크립트 체크패스워드 함수)

sign-in endpoint를 구현할 때, database에 저장된 password와 client에서 보낸 password를 비교할 때 사용한다.


bcrypt.checkpw('1234'.encode('utf-8'), hashed_password)
# True (boolean 형태로 반환함)

첫 번째 인자는 client가 입력한 password, 두 번째 인자는 hash된 password로 들어간다.
즉, password가 hash되고 decode되어서 str로 저장된 password이다.
database에서 password는 decode 된 상태로 저장 되어 있기 때문에(str) checkpw 함수 또한 password는 encode해서(byte type으로 변환) 넣어줘야 하므로 즉, database에서 꺼내 오면 다시 encoding을 해서 비교한다.

#jwt 설치
pip install pyjwt

import jwt

bcrypt.checkpw(data['password'].encode('utf-8'), user.password.encode('utf-8')):

access_token = jwt.encode({'id':user.id}, SECRET_KEY, algorithm = ALGORITHM)

#print(jwt.decode(access_token, SECRET_KEY, algorithms = ALGORITHM))
{'id' : 72}
               

인가(Authorization)

사용자가 서버에 로그인 하면 해당 사용자가 맞는지 확인하는 과정
stateless한 성질(저장하지 않는 성질)이 있기 때문에 사용자는 매번 로그인을 해야 하는데, 서버는 사용자가 로그인 했을 경우, 로그인 했다는 것을 아는 방법은 frontend가 headers에 메타데이터를 보내면 서버가 확인하는데 이 메타데이터에 Json Web Token이 담겨져있다.

jwt = header + payload + signature

인코드된 header와 payload, signature(secret)을 헤더에 지정된 암호 알고리즘으로 암호화하여 전송


프론트엔드가 API server로 headers의 autherization에 jwt token을 넣어서 request하면 백엔드 server에서는 전송받은 JWT에 담겨 있는 header, payload, signature를 확인해서 우리 service의 server에서 생성한 JWT가 맞는지 확인한다(내가 가진 secret key를 algorithm과 user id로 암호화를 해서, 프론트엔드가 보낸 것과 맞는지 확인한다)

payload 	 = jwt.decode(access_token, SECRET_KEY, algorithms = ALGORITHM) 

회원가입하고 로그인 후 로그인에 성공적이면 백엔드에서 토큰을 프론트에게 전송.
프론트는 웹에 저장하고 로그인이 필요한 요청을 백엔드서버에게 보낼 때 백엔드가 분석해주는데, 그것을 해주는 라이브러리가 bcrypt이다.

프론트로부터 토큰을 받았을 때, 내가 가진 토큰을 암호화해서 맞으면 우리 서버에서 서비스에서 발급한 토큰임을 확인하고 원본을 대조하여, 내 서비스 유저인지 구분하고 인가해줌


- payload

사용자와 관련된 내용을 encoding(암호화된 것은 아님)해서 넣어주는데, 개인정보를 담으면 안되며, user id는 실제 아이디가 아니라 database에 저장되는 PK 형태로 담아야 한다. 백엔드 개발자만 알 수 있는 숫자 정보(만료 시간도 담아줄 수 있다) 등을 담는다.

개발자는 사용자의 관리자라 할지라도 사용자의 이메일과 비밀번호 hashing이 필요한 개인정보 등을 개발자도 알 수 없도록 데이터 베이스를 관리해야한다.

https://pyjwt.readthedocs.io/en/latest/
https://pypi.org/project/bcrypt/

profile
킴릴로

0개의 댓글