JSON Web Token์ ๊ตฌ์กฐ๋ HEADER, PAYLOAD, SIGNATURE๋ก ๊ตฌ์ฑ๋์ด์๋ค.
์์๊ฐ์ด https://jwt.io ์์ ๋ฐํ๋ JWT์ payload๋ฅผ ๊บผ๋ด์ด๋ณผ ์ ์๊ธดํ์ง๋ง
signature์ key๊ฐ์ ๋ชจ๋ฅธ๋ค๋ฉด ์์๋ก ํ ํฐ์ ๋ฐํํ์ฌ ์๋ฒ๋จ์์ ํด๋น์ ์ ์ ๋ํ ๊ถํ์ ๋ถ์ฌ๋ฐ์ ์ ์๊ธฐ ๋๋ฌธ์ ๋ณด์์ด ๊ฐ๋ฅํ ๊ตฌ์กฐ์ด๋ค.
payload์ ๊ณ ์ ์๋ณ์ ๋ณด๋ฅผ ๋ด์ ๊ฒฝ์ฐ,
user_id or ํน์ pk๊ฐ์ ๋ํด front๋ก๋ถํฐ ๋จ๋ฐฉํฅ ํด์ ์๊ณ ๋ฆฌ์ฆ์ ์ ์ฉ(ํค ์คํธ๋ ์นญ)ํ์ฌ ๋๊ฒจ๋ฐ๋๋ค.
from django.http import JsonResponse
from .models import User
import os
import jwt
import datetime
def jwt_publish(login_id):
jwt_key = os.environ.get('JWT_KEY')
jwt_expiration = datetime.datetime.utcnow() + datetime.timedelta(seconds=60*60*6)
access_jwt = jwt.encode({'exp': jwt_expiration, 'login id': login_id}, key=jwt_key['SECRET KEY'], algorithm=jwt_key['ALGORITHM'])
return access_jwt
def jwt_authorization(func):
def wrapper(self, request, *args, **kwargs):
jwt_key = os.environ.get('JWT_KEY')
try:
try:
access_jwt = request.COOKIES.get('_utk')
except:
return JsonResponse({'message': 'GET JWT COOKIE ERROR'}, status=400)
# decode
payload = jwt.decode(access_jwt, key=jwt_key['SECRET KEY'], algorithms=jwt_key['ALGORITHM'])
login_id = payload['login id']
try:
login_user = User.objects.get(login_id=login_id)
except:
return JsonResponse({'message': 'GET USER ERROR'}, status=400)
request.user = login_user
return func(self, request, *args, **kwargs)
except jwt.ExpiredSignatureError:
return JsonResponse({'message': 'JWTOKEN EXPIRED'}, status=401)
except jwt.InvalidTokenError:
return JsonResponse({'message': 'INVALID JWTOKEN'}, status=401)
return wrapper
jwt_publish ํจ์์์๋ jwt๋ฅผ ๋ฐํํ๊ธฐ ์ํด secret key๊ฐ์ธ jwt_key๋ฅผ ํ๊ฒฝ๋ณ์์์ ๋ฐ์์ค๊ณ ๋ง๋ฃ์๊ฐ(jwt_expiration)์ ์ง์ ํด์ค๋ค.
payload๋ถ๋ถ์๋, dictํ์์ ๊ฐ์ฒด๋ก ์ง์ ํด์ค ๋ง๋ฃ์๊ฐ์ default key๊ฐ์ธ exp๋ฅผ ํตํด key-value๋ก ํ ๋น์์ผ์ฃผ๊ณ
์ ๋ฌํ๊ณ ์ ํ๋ ๊ณ ์ ์๋ณ๊ฐ์ ๋ํด(๋ก๊ทธ์ธ/ํ์๊ฐ์
์encrypted๋ ๋ฐ์ดํฐ๋ฅผ)์๋ ๋ง์ฐฌ๊ฐ์ง๋ก key-value('login_id': login_id)๋ก ๋ฃ์ด์ค๋ค.
jwt_authorization ๋ฐ์ฝ๋ ์ดํฐ ํจ์์์๋ ๋ฐํ๋ jwt์ ๋ํด, ํน์ ๊ถํ์ด ํ์ํ API๊ฐ ์์ฒญ๋๋ฉด ๋ฐ์ฝ๋ ์ดํฐ์ ์ํด ๊ถํ์ด ์กด์ฌํ๋์ง ์ฌ๋ถ๋ฅผ ํ๋ณํ๋ค.
์ฟ ํค์ jwt๋ฅผ ์ ์ฅํ๋ ๋ฐฉ์์ ์ฑํํ๊ธฐ ๋๋ฌธ์ ์์ฒญ๋๋ API์์ ์ฟ ํค๋ฅผ ์ฝ์๋ค์ jwt_key๊ฐ์ ํตํด decodeํด์ค๋ค.
๊ทธ๋ค์ ์์ฒญํ ์ ์ ์ ๋ํด ๊ณ ์ ์๋ณ๊ฐ์ ํ์ธ/์ฌ์ฉํ๊ธฐ ์ํด request.user๋ก ๊ณ ์ ์๋ณ๊ฐ์ ํ ๋น์์ผ์ค๋ค.
์๋๋ JWT๋ฐ์ฝ๋ ์ดํฐ๋ฅผ ์ ์ฉํ ์์์ฝ๋์ด๋ค.
class item:
'''
'''
@jwt_authorization
def get(self, request, *args, **kwargs):
order_pk = order.object.filter(user_pk=request.user)._pk
'''
'''
return JsonResponse({'message': 'GET ORDER LIST SUCCESS', 'data': orders}, status=200)
์์์ฝ๋์ ๊ฐ์ด ํน์ ์ ์ ๊ฐ ์ฃผ๋ฌธํ ๋ฌผํ๋ค์ ๋ํ ๋ฆฌ์คํธ๋ฅผ ๋ฐ์์ฌ๋,
@jwt_authorization์ ์ํด ๋ด๋ถ payload๊ฐ์ด ๊บผ๋ด์ง๊ฒ ๋๊ณ
์์ฒญ๋ ํน์ ์ ์ ์ ์ฐ๊ฒฐ๋ DB์ ์ ๊ทผํ ์ ์๋ค.
์ฟ ํค | ์ธ์ | |
---|---|---|
์ ์ฅ ์์น | ํด๋ผ์ด์ธํธ | ์๋ฒ |
์ ์ฅ ํ์ | ํ ์คํธ | ์ค๋ธ์ ํธ |
๋ง๋ฃ์์ | ์ฟ ํค ์ ์ฅ์ ์ค์ * | ๋ธ๋ผ์ฐ์ ์ข ๋ฃ์ ์ญ์ * |
๋ฆฌ์์ค | ํด๋ผ์ด์ธํธ ๋ฆฌ์์ค | ์๋ฒ ๋ฆฌ์์ค |
์ฉ๋ | ์ด 300๊ฐ, ํ๋์ ๋๋ฉ์ธ ๋น 20๊ฐ, ํ๋์ ์ฟ ํค ๋น 4kb(4096byte) | ์๋ฒ์ฉ๋๋งํผ ๊ฐ๋ฅ |
์๋ | ์ธ์ ๋ณด๋ค ๋น๊ต์ ๋น ๋ฆ | ์ฟ ํค๋ณด๋ค ๋น๊ต์ ๋๋ฆผ |
๋ณด์ | ์ธ์ ๋ณด๋ค ๋น๊ต์ ์์ข์ | ์ฟ ํค๋ณด๋ค ๋น๊ต์ ์ข์ |
์ ์ ๊ฐ ๋ก๊ทธ์ธ์ ํตํด ํน์ ๊ถํ ๋ฐ ๋ณธ์ธ์ธ์ฆ ์ ๋ณด๋ฅผ ๊ฐ์ง๊ณ ์น์๋น์ค๋ฅผ ์ด์ฉํ ๋,
์ฟ ํค์ ์์ฑ๋ค์ ํตํด ๋ณด์์ ๊ฐํํ๋ค๋ฉด ์ธ์
์ ๋นํด ๊ฐ์ ธ๊ฐ ์ ์๋ ์ด์ ๋ค์ด ๋ง๋ค.
๊ทธ ์๋ก, ์ผํ๋ชฐ์ ๊ฒฝ์ฐ ์ฅ๊ธฐ๊ฐ ๋ก๊ทธ์ธํ๋ ๊ฒฝ์ฐ๋ณด๋ค ์ฌ๋ฌ ๋ธ๋ผ์ฐ์ ์์ ์ ์ํ์ฌ ์ํ์ ๋ณด๊ฑฐ๋ ๊ตฌ๋งค๋ด์ญ ๋ฐ ๊ฒฐ์ ๊ธฐ๋ฅ์ ๋ํ์ฌ ๊ถํ์ธ์ฆ์ ๋ํด ์ฒ๋ฆฌํ๋ ๊ฒฝ์ฐ๊ฐ ์ฃผ๋ฅผ ์ด๋ฃจ๋ค๋ณด๋ ์ฟ ํค ์ฌ์ฉ์์ ๊ทธ ์ด์ ์ด ์๋ ๊ฒ ๊ฐ๋ค.
- expire
์ฟ ํค ๋ง๋ฃ๊ธฐํ์ ์ค์ ํ์ฌ ํน์ ์ ์ ์ ์ํ ๊ถํ์ธ์ฆ๊ธฐํ์ ๋ถ์ฌํ๋ ๊ฒ์ด๋ค.
JWT๋ฅผ ์ฌ์ฉํ ๋ ๋ก๊ทธ์์ํ ๊ฒฝ์ฐ ์ฟ ํค ๋ง๋ฃ๊ธฐํ๊ณผ ์๊ด์์ด blacklist๋ก ๊ด๋ฆฌํ์ฌ ์ด๋ฏธ ๋ก๊ทธ์์ ์ฒ๋ฆฌ๋ ํ ํฐ๊ฐ์ ๋ํด ๊ถํ์ด ์๋ ์ํ๋ก ๋ง๋ค ์ ์๋ค.
- domain
ํ๋์ ๋๋ฉ์ธ์์์ ์ฌ๋ฌ ์๋ฒ๋ฅผ(for example: Apple.com, api.Apple.com, admin.Apple.com)๋๋ ๊ฒฝ์ฐ ์ฟ ํค๋ฅผ ๊ณต์ ํ ์ ์๋๋ก ์ค์ ํ๋ค.
- secure
https์ธ์ ํ๊ฒฝ์์ ์ฟ ํค๊ฐ์ ์ ๋ฌํ์ง ์๋๋ค
- httponly
httponly = False์ผ ๋๋ Javascript์์ ์ฟ ํค๊ฐ์ ๋ํ ์ ๊ทผ์ด ๊ฐ๋ฅํ์ง๋ง
httponly = True์ผ ๋๋ XSS(Cross-Site Scripting)๋ก ์ธํด ์ทจ์ฝํ ๋ถ๋ถ์ด ์๊ธธ์ง๋ผ๋ ์ฟ ํค๊ฐ์ ํ์ทจ๋ ๋ง์ ์ ์๋ค.
(XSS๋ ์น ์ ํ๋ฆฌ์ผ์ด์ ์์ ์ผ์ด๋๋ ์ทจ์ฝ์ ์ผ๋ก ๊ด๋ฆฌ์๊ฐ ์๋ ๊ถํ์ด ์๋ ์ฌ์ฉ์๊ฐ ์น ์ฌ์ดํธ์ ์คํฌ๋ฆฝํธ๋ฅผ ์ฝ์ ํ๋ ๊ณต๊ฒฉ ๊ธฐ๋ฒ์ด๋ค, XSS์ CSRF๊ณต๊ฒฉ์ ์ฐจ์ด์ ๋ ๊ณง...)
- samesite
ํ๋ก ํธ์ ๋ฐฑ์๋๊ฐ ๊ฐ๊ฐ์ ์๋ฒ๋ฅผ ์ด์ํ ๋, ๊ฐ๊ฐ์ domain์ ๋ํด์ ์ ํ์ฌํญ์ ์ค์ ํ๋ ์์ฑ์ด๋ค.
samesite์ ๋ํด์ ์ผ๋ฐ์ ์ผ๋ก ๋ช ์๋๋ฐ ์์ด default๊ฐ์ None๊ฐ์ด์์ง๋ง,
20๋ 2์ 4์ผ ๋ฐฐํฌ๋ ํฌ๋กฌ80์์๋ samesite์ default๊ฐ์ด lax๋ก ๋ณ๊ฒฝ๋์๋ค.
- if samesite = None:
์ด์ ์๋ ์๋ก ๋ค๋ฅธ ๋๋ฉ์ธ์ธ Apple.com -> Banana.com ์ฟ ํค๊ฐ ์ฌ์ฉ๋ ์ ์์์ผ๋ ํฌ๋กฌ80์์๋ ๋ถ๊ฐ๋ฅํ๊ฒ ๋์๋ค.- if samesite = lax:
๋ค๋ฅธ ๋๋ฉ์ธ์ด์ด๋ ์ ํ์ ์ธ http method์ ๋ํด์ ์) ์๋ฒ์ ์ฌ์(ํน์ ์ํ)์ ๋ํด ์ํฅ์ ๋ผ์น์ง ์๋ get์์ฒญ์ ๋ํด์๋ ์ฟ ํค์ด๋์ด ๊ฐ๋ฅํ๋ค.- if samsite = strict:
resource๋ฅผ ์ ๊ณตํ๋ ๋๋ฉ์ธ๊ณผ ์ฌ์ฉํ๋ ๋๋ฉ์ธ์ฌ์ด์ ๋๋ฉ์ธ์ด ์ผ์นํ์ฌ์ผ ์ฟ ํค๊ฐ ์ด๋ํ๋ค.
*{domain}๊ฐ์ ๋ฑ๋กํ ๋๋ฉ์ธ๋ช
# Django https & domain ํ๊ฒฝ์์ ์ฟ ํค ์ค์
from .base import *
import os
DEBUG = False
SESSION_COOKIE_SECURE = True
SESSION_COOKIE_DOMAIN = '{domain}.com'
SESSION_COOKIE_SAMESITE = 'lax'
CSRF_COOKIE_SECURE = True
CSRF_COOKIE_DOMAIN = '{domain}.com'
CSRF_COOKIE_SAMESITE = 'lax'
CSRF_TRUSTED_ORIGINS = ['api.{domain}.com', 'https://api.{domain}.com']
DATABASES = os.environ.get('DATABASES')
ALLOWED_HOSTS += [
'{domain}.com',
'api.{domain}.com'
]
CORS_ALLOWED_ORIGINS += [
'https://{domain}.com',
'https://admin.{domain}.com',
]
CORS_ALLOW_HEADERS += [
# etc
]
# JsonResponse๋ก ์ฟ ํค ๋ฐ๊ธํ๊ธฐ
from django.http import JsonResponse
'''
'''
class CustomerView(View):
@csrf_exempt
@require_http_methods(['POST'])
def signup(request):
access_jwt = jwt_publish(user_id)
'''
'''
response = JsonResponse({'message': '{domain} signup success', json_dumps_params={'ensure_ascii':False}, status=201)
response.set_cookie(
key='_utk', # ํค๊ฐ ์ง์
value=access_jwt,
expires=datetime.timedelta(hours=6), # ์ฟ ํค ๋ง๋ฃ๊ธฐํ ์ค์
path="/",
domain='{domain}.com',
secure=True,
httponly=True,
samesite="lax",
)
return response
์ด์ธ์ ๋ฐฉ๋ฒ์ผ๋ก๋ simple-jwtํจํค์ง๋ฅผ ์ด์ฉํ๋ฉด header๋ฅผ ํตํด ํ ํฐ์ธ์ฆ ์ฒ๋ฆฌ ๋ฐ ํ ํฐ ๋ธ๋๋ฆฌ์คํธ ๊ด๋ฆฌ๋ฅผ ํ ์ ์๋ค.