Authentication/Authorization


認証
ユーザーIDの検証プロセス(ユーザーのidとパスワードの検証)
ネットワークを介してパスワードを渡すと、ネットワークを介してデータが露出する可能性があるため、httpsを使用してセキュリティが強化され、APIサービスはクライアントが入力した値を特定のアルゴリズムでハッシュしてデータベースに格納します.
salt + key stretching
このときsalting,keystretchingを実行して一方向hashを補完する
ユーザーがログインに成功すると、サーバはtokenを送信します.(一方向ハッシュの場合)アルゴリズムが1つあれば、入力するたびに同じハッシュ値しか持たないので、これを補うために、ハッシュの前にsaltingでランダム値を加算します.
結果はhash値が異なりますが、後で比較するためsalt値も保存されます.

- salting
実際のパスワードに加えてrandom salt値が付加されているため、同じパスワード「1234」を入力しても異なる結果が得られる.
- key stretching
複数回ハッシュを繰り返す
- bcrypt
ハッシュパスワード用のライブラリ.saltingとkeystretchingを実現する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エンドポイントを実装する場合、データベースに格納されているパスワードとクライアントが送信するパスワードを比較します.

bcrypt.checkpw('1234'.encode('utf-8'), hashed_password)
# True (boolean 형태로 반환함)
1番目のパラメータはclientが入力したパスワードを入力し、2番目のパラメータはハッシュパスワードを入力します.
すなわち,パスワードはhashとdecodeによってstrのパスワードとして格納される.
データベースではpasswordがdecode状態として格納されるため(str)checkpw関数もpasswordを符号化して入れる(bytetypeに変換)、つまりデータベースから取り出してから符号化して比較する必要がある.
#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}
               
権限
ユーザーがサーバにログインしたときに、ユーザーが正しいかどうかを確認します.
無状態の性質(保存しない性質)を持つため、ユーザは毎回ログインしなければならないが、サーバがユーザログインを知っている場合、ログインの方法はfrontendがメタデータをHeadersに送信したときにサーバによって確認され、このメタデータにはJson Web Tokenが含まれている.
jwt = header + payload + signature
符号化されたヘッダと負荷、署名(secret)を暗号化し、ヘッダに指定されたパスワードアルゴリズムを送信する
フロントエンドがjwt tokenをAPIサーバとしてHeadersのauthenticationに要求すると、バックエンドサーバが送信するJWTに含まれるHeader、ペイロード、signatureを確認し、私たちのサービスのサーバーで生成されたJWTが正しいかどうかを確認します(私が持っているsecret keyをアルゴリズムとuseridで暗号化して、フロントエンドで送信されたかどうかを確認します).
payload 	 = jwt.decode(access_token, SECRET_KEY, algorithms = ALGORITHM) 
登録してログインに成功すると、バックエンドはフロントにトークンを送信します.
フロントがログインする必要があるリクエストをWebに保存してバックエンドサーバに送信すると、バックエンドはそれを分析し、そのライブラリはbcryptです.
フロントのコインを受け取る時、もし私のコインが暗号化された後に打たれたら、それが私たちのサーバーが発行したコインであることを確認して、原本をチェックして、それが私のサービスユーザーであるかどうかを区別して認めます.
- payload
ユーザに関連するコンテンツを符号化(暗号化ではない)して入れ、個人情報を含めることはできず、ユーザidは実際のアイデンティティではなくPK形式でデータベースに格納すべきである.バックエンド開発者のみが知っているデジタル情報(期限切れも含む)などが含まれています.
ユーザーの管理者であっても、開発者はユーザーのEメールとパスワードのハッシュが必要な個人情報を知らないようにデータベースを管理する必要があります.
https://pyjwt.readthedocs.io/en/latest/
https://pypi.org/project/bcrypt/