์ธ์ฆ(Authentication)์ ์ฌ์ฉ์๊ฐ ์์ ์ด ์ฃผ์ฅํ ์ฌ๋์ด ๋ง๋์ง ํ์ธํ๋ ์ ์ฐจ๋ฅผ ๊ฑฐ์นฉ๋๋ค. ์ด๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์ฌ์ฉํ ๊ธฐ๋ฅ์ด ๋ก๊ทธ์ธ, ๋ก๊ทธ์์, ๊ทธ๋ฆฌ๊ณ ํ์๊ฐ์ ์ด ์์ต๋๋ค. ์ด๋ฅผ ์ํด Web์์๋ ์ฌ์ฉ์์ ID์ Password๋ฅผ ๋ฐ์์ ๋ฑ๋กํ ์ฌ์ฉ์๊ฐ ๋ง๋์ง ๊ฒ์ฆ์ ํฉ๋๋ค. ์ด๋ฅผ ์คํํ ๋ ์ ์ ์ ๋น๋ฐ๋ฒํธ๋ ์ ๋๋ก DB์ ๊ทธ๋๋ก ์ ์ฅํ์ง ์์ต๋๋ค. ๊ทธ ์ด์ ๋ ๋ง์ผ์ DB๊ฐ ๋ค๋ฅธ ํ๊ฐ๋ฐ์ง ์๋ ์ฌ๋๋ค์๊ฒ ๋ ธ์ถ์ด ๋ ๊ฒฝ์ฐ ๊ฐ์ธ์ ๋ณด๊ฐ ๋ณดํธ ๋ฐ์ ์ ์๊ธฐ ๋๋ฌธ์ ๋๋ค.
์ผ๋ฐ์ ์ผ๋ก ๋น๋ฐ๋ฒํธ๋ฅผ ์ํธํ๋ฅผ ํ ๋๋ ๋จ๋ฐํฅ ํด์ฌ ํจ์(One-way hash function)
์ด ์ฐ์
๋๋ค. ๋จ๋ฐฉํฅ์ด๋ผ๊ณ ๋ถ๋ฆฌ๋ ์ด์ ๋ก๋ ์๋ณธ ๋ฉ์ธ์ง๋ฅผ ๋ณํํ์ฌ ์์ฑ๋ ์ํธํ๋ ๋ฉ์ธ์ง(digest)๋ง์ผ๋ก๋ ๋ค์ ์๋ณธ์ผ๋ก ๊ฐ ์๊ฐ ์๊ธฐ ๋๋ฌธ์
๋๋ค.
์ฌ์ฉ์๊ฐ ๋ง๋์ง ์ธ์ฆ์ ๊ตฌํํ๊ธฐ ์ด์ ์ Web์์๋ ์ฐ์ ์ ์ผ๋ก ์ฌ์ฉ์์ ์ ๋ณด๋ฅผ ๋ณดํธํ ํ์๊ฐ ์์ต๋๋ค. HTTP๋ฅผ ํตํด ์ฌ์ฉ์์ ์ ๋ณด๊ฐ ํต์ ๋๋ ์์ค์ ๋ค๋ฅธ ์ฌ๋์ด data๋ฅผ ๊ฐ์ ธ๊ฐ ์ ๋ณด๋ฅผ ๋ณผ ์๊ฐ ์์ต๋๋ค.
์ด๋ฅผ ๋ณดํธํ๊ธฐ ์ํด์ ์ฐ๋ฆฌ๋ ํ์์ ์ผ๋ก ๋น๋ฐ๋ฒํธ๋ฅผ ์ํธํํด์ ์ค๊ฐ์ ์ ๋ณด๊ฐ ์์ด๋๊ฐ๋๋ผ๋ ์์๋ณผ์ ์๊ฒ ํฉ๋๋ค. Salting๊ณผ Key Stretching์ ๊ตฌํํ ํด์ฌ ํจ์์ค ๊ฐ์ฅ ๋๋ฆฌ ์ฌ์ฉ๋๋ ๊ฒ์ด bcrypt
์
๋๋ค. bcrypt๋ ์ฒ์๋ถํฐ ๋น๋ฐ๋ฒํธ๋ฅผ ๋จ๋ฐฉํฅ ์ํธํ ํ๊ธฐ ์ํด ๋ง๋ค์ด์ ํด์ฌํจ์ ์ด๋ค. ๊ทธ ์ฒซ๋ฒ์งธ ๊ณผ์ ์ผ๋ก bcrypt๋ฅผ ์ค์นํฉ๋๋ค.
pip install bcrypt
๊ทธ ๋ค์์ผ๋ก๋ python interpreter๋ฅผ ์คํํ๊ณ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ importํฉ๋๋ค.
>>> import bcrypt
๋จ๋ฐฉํฅ ํด์ฌ ํจ์์ ์ทจ์ฝ์ ๋ค์ ๋ณด์ํ๊ธฐ ์ํด ์ผ๋ฐ์ ์ผ๋ก 2๊ฐ์ง ๋ณด์์ ๋ค์ด ์ฌ์ฉ๋ฉ๋๋ค.
- Salting
์ค์ ๋น๋ฐ๋ฒํธ ์ด์ธ์ ์ถ๊ฐ์ ์ผ๋ก ๋๋ค ๋ฐ์ดํฐ๋ฅผ ๋ํด์ ํด์๊ฐ์ ๊ณ์ฐํ๋ ๋ฐฉ๋ฒ.- Key Stretching
๋จ๋ฐํฅ ํด์ฌ๊ฐ์ ๊ณ์ฐ ํ ํ ํด์ฌ๊ฐ์ ๋ ๋ค์ ํด์ฌํ๊ณ ๊ทธ ๋ค์ ํด์ฌํ๋ ๊ณ์๋ ํด์ฌ ๋ฐ๋ณต
bcrypt๋ str
ํ์์ data๊ฐ ์๋ bytes
ํ์์ data๋ฅผ ์ํธํํฉ๋๋ค. ๋ฐ๋ผ์ ์ํธํ์์๋ bytesํ์์ผ๋ก ๋ณํ์ ํด์ผํฉ๋๋ค.
str
์ encodeํ๋ฉด bytes(์ด์งํ)๋๊ณ , Bytes
๋ฅผ decodeํ๋ฉด str
ํ์์ผ๋ก ๋ค์ ๋์๊ฐ๋๋ค. UTF-8
์ ๋์ฝ๋ ๋ฌธ์ ๊ท๊ฒฉ์ ์ฌ์ฉํฉ๋๋ค. #๋น๋ฐ๋ฒํธ 1234 ์ํธํ ์์
password = '1234'
hashed_password = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt())
print(hashed_password)
b'$2b$12$YFs9rh.1LgJwZuf9ibyjpuLvBoCaGX0MzedFWF2Jo0zU3lMZurZ4a'
#ํด์๋ ๋น๋ฐ๋ฒํธ์ ๊ฐ
์์ ์ฝ๋๋ฅผ ํตํด ๋น๋ฐ๋ฒํธ๊ฐ ์ํธํ๋ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค.
์ด์ ๋น๋ฐ๋ฒํธ๋ฅผ ์ํธํ๊ฐ ๋์์ต๋๋ค. ๋น๋ก ์ํธํ๊ฐ ๋์์ง๋ง ์ฌ์ฉ์๊ฐ ์ด ๋น๋ฐ๋ฒํธ๊ฐ ๋ง๋์ง ํ์ธํ๋ ๋ฐฉ๋ฒ์ด ์์ต๋๋ค. bcrypt.checkpw()
method๋ฅผ ์ด์ฉ์ ํ๋ฉด ์ฌ์ฉ์๋ ์ํธ๊ฐ ๋น๋ฐ๋ฒํธ์ ์ผ์น๋ฅผ ํ๊ฒ ๋๋ฉด True
๊ฐ์ ๋ฐ๊ฒ ๋ฉ๋๋ค.
>>> new_password = '1234'
>>> bcrypt.checkpw(new_password.encode('utf-8'),hashed_password)
>>> True
์์ ์๋ new_password.encode('utf-8)
์ ์ฌ์ฉ์๋ก๋ถํฐ ์
๋ ฅ๋ฐ์ ํจ์ค์๋์ด๊ณ hashed_passoword
๋ ์ ์ฅ๋ ์ํธํ๋ ํจ์ค์๋์
๋๋ค. ๋ ํจ์ค์๋ ๋ค data type์ bytes
์
๋๋ค.
๐จโ๐ปBcrypt๋ ๋ช๊ฐ์ง์ ์ทจ์ฝ์ ์ด ์กด์ฌํฉ๋๋ค.
- Rainbow table attack: ๋ฏธ๋ฆฌ ํด์ฌ๊ฐ๋ค์ ๊ณ์ฐํด ๋์ ํ ์ด๋ธ์ ์ด์ฉํ๊ฒ ๋๋ค๋ฉด ์ญ์ผ๋ก ์ํธํ๋ ๋น๋ฐ๋ฒํธ๋ฅผ ์ ์ถํ ์ ์๊ฒ ๋ฉ๋๋ค.
์ ์ ๊ฐ ๋ก๊ทธ์ธ์ ์ฑ๊ณตํ ํ์๋ access token
์ด๋ผ๊ณ ํ๋ ์ํธํ๋ ์ ์ ์ ๋ณด๋ฅผ ์ฒจ๋ถํด์ request๋ฅผ ๋ณด๋ด๊ฒ ๋๋ค.
POST /auth HTTP/1.1
Host: localhost:5000
Content-Type: application/json
{
"username": "joe",
"password": "pass"
}
HTTP/1.1 200 OK
Content-Type: application/json
{
"access_token" : "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZGVudGl0eSI6MSwiaWF0IjoxNDQ0OTE3NjQwLCJuYmYiOjE0NDQ5MTc2NDAsImV4cCI6MTQ0NDkxNzk0MH0.KPmI6WSjRjlpzecPvs3q_T3cJQvAgJvaQAPtk1abC_E"
}
access token
์ ๋ฐ์ ์๋ฒ๋ ํ ํฐ์ ๋ณตํธํ ํด์ ํด๋น ์ ์ ์ ์ ๋ณด๋ฅผ ์ป๊ฒ๋ฉ๋๋ค. ์ด๋ฐ์์ผ๋ก ํ ํฐ์ ๋ฐ๋ ์ด์ ๋ ํด๋น ์ ์ ๊ฐ ๋งค๋ฒ ๋ก๊ทธ์ธ์ ํ์ง ์๋๋ก ํ๋๊ฒ์ด๋ค.
์ด๋ฐ access token
์ ์์ฑํ๋ ๋ฐฉ๋ฒ์ ์ฌ๋ฌ๊ฐ์ง๊ฐ ์กด์ฌํ๋๋ฐ ๊ฐ์ฅ ๋๋ฆฌ ์๋ ค์ง ๊ธฐ์ ์ด JWT(JSON Web Tokens) ์ด๋ค. JWT๋ ์ ์ ์ ๋ณด๋ฅผ ๋ด์ JSON ๋ฐ์ดํฐ๋ฅผ ์ํธํํด์ Client์ Server๊ฐ์ ์ฃผ๊ณ ๋ฐ๋ ๊ฒ์ ๋ปํฉ๋๋ค.
JWT ๊ตฌ์กฐ:
์์ ๊ตฌ์กฐ๋ฅผ ๋ณด๋ฉด Header, Payload, ๊ทธ๋ฆฌ๊ณ Signature ์ธ ๋ถ๋ถ์ผ๋ก ๊ตฌ์ฑ์ด ๋์ด ์์ต๋๋ค.
๐ก Signature ๋ถ๋ถ ์ด์ธ์ ๋๋จธ์ง ๋ถ๋ถ์ ์ํธํ๊ฐ ์๋ ์ธ์ฝ๋ฉ ํ์ฌ ์ ์กํ๋ค. ๋ฐ๋ผ์ ๋๊ตฌ๋ ์๋ณธ ๋ฐ์ดํฐ๋ฅผ ์ฝ์ ์ ์๊ธฐ ๋๋ฌธ์ ๋ฏผ๊ฐํ ๋ฐ์ดํฐ๋ ์ ์ฅํ์ง ์๋๋ก ์ฃผ์ํ์ฌ์ผ ํ๋ค,
PyJWT ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ค์นํ๊ธฐ:
pip install pyjwt
bcrypt์ ๋์ผํ๊ฒ python interpreter์คํํ๊ณ . ๋ค์๊ณผ ๊ฐ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ๋์์ํฌ ์ ์์ต๋๋ค.
import jwt #ํจํค์ง๋ช
์ pyjwt์ด์ง๋ง ์ํฌํธํ ๋์ ์ด๋ฆ์ jwt์
๋๋ค.
SECRET = 'secret' #'๋๋คํ ์กฐํฉ์ ๋น๋ฐ๋ฒํธ' ์์ ์ด๋ฏ๋ก ๋จ์ํ๊ฒ 'secret'์ด๋ผ๊ณ ํ๊ฒ ์ต๋๋ค.
access_token = jwt.encode({'id' : 1}, SECRET, algorithm = 'HS256')
print(access_token)
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6MX0.-xXA0iKB4mVNvWLYFtt2xNiYkFpObF54J9lj2RwduAI'
๐ก JWT์ ๊ฒฐ๊ณผ๋ฌผ์ pyjwt์ ๋ฒ์ ์ ๋ฐ๋ผ bytes(ver. 1.7)ํ์ ๋๋ str(ver. 2.0 ์ด์)ํ์ ์ ๋๋ค.
์ด์ ๋ง๋ค์ด์ง ์ธ์ฆ์ ์ํ ๋งค๊ฐ์ฒด๋ฅผ JWT Token, ์ฆaccess_toekn
์ ๋ง๋ค์์ต๋๋ค. ์ด ๋ฐ๊ธ๋ token์ ๋ก๊ทธ์ธ์ด ์ฑ๊ณตํ๊ฒ ๋๋ฉด Frontend engineer์๊ฒ ์ ๋ฌ์ ํด์ผ ํฉ๋๋ค.
์ธ์ฆ์ ํต๊ณผํ ์ฌ์ฉ์๋ง ์ ๊ทผ์ ํ๊ฒ ํ๋ ค๋ฉด token์ ๋ฐ์์ ๋ฐํ๋ token์ด ๋ง๋์ง ํ์ธํ๋ ์ ์ฐจ๋ฅผ ๊ฑธ์นฉ๋๋ค.
header = jwt.decode(access_token, SECRET, algorithm = 'HS256')
print(header)
{'id': 1}
# decode ๊ฒฐ๊ณผ๋ ์ฐ๋ฆฌ๊ฐ encode ํ ๋ ๋๊ฒผ๋ header๊ฐ์ธ {'id' : 1}์
๋๋ค.
์ด header ๊ฐ์ ํตํด ์ ์ ๋ฅผ ์๋ณํ ์ ์์ต๋๋ค. ๊ทธ๋ฆฌ๊ณ ์ธ์ฆํ๋ ์ฝ๋๋ endpoint์ ๋ฐ์ฝ๋ ์ดํฐ๋ฅผ ๊ตฌํํด์ผ ํ๊ณ ๊ตฌํ์ ๋ณดํต user app์ utils.py๋ฅผ ๋ง๋ค์ด์ ์์ฑํฉ๋๋ค.