로그인 - jwt, Redis, stateless vs stateful, cookie vs session
06 Sep 2021 | 입문 TIL회원가입/로그인이 이루어질 때 jwt를 발급하고 이를 통해 유저를 알아본다는 것까지 학원에서 실습 및 구현을 했었다. 기업협업 중에 admin/superadmin 데코레이터를 만드는데 pay_load에 넣은 role이 반영되지 않아서 하루동안 고민했는데 사수분께서 pay_load에 뭔가 새로 추가했으면 엑세스토큰을 다시 발급받았어야된다면서 jwt의 구성요소에 대해서 설명해주고 아래 개념들에 대해서 공부해보라고 하셨다.
참고로 첨부하는 admin_only 데코레이터
def admin_only(func):
def wrapper(self, request, *args, **kwargs):
try:
access_token = request.headers.get('Authorization')
pay_load = jwt.decode(access_token, SECRET_KEY, algorithms=[ALGORITHM])
role = pay_load['role']
user = User.objects.get(id=pay_load['user_id'])
request.user = user
if not role == 'admin':
return JsonResponse({'message': 'UNAUTHORIZED'}, status=401)
return func(self, request, *args, **kwargs)
except jwt.InvalidTokenError:
return JsonResponse({'message': 'INVALID_TOKEN'}, status=401)
except jwt.exceptions.DecodeError:
return JsonResponse({'message': 'DECODE_ERROR'}, status=400)
except jwt.ExpiredSignatureError:
return JsonResponse({'message': 'EXPIRED_TOKEN'}, status=401)
except User.DoesNotExist:
return JsonResponse({'message': 'USER_DOES_NOT_EXISTS'}, status=401)
except KeyError:
return JsonResponse({'message': 'KEY_ERROR'}, status=400)
jwt
JWT에 대해서 알고자 한다면 Auth0에서 만든 JWT 사이트가 참고하기 제일 좋다. 이 사이트에서 JWT 토큰을 테스트해보거나 구조도 파악해 볼 수 있고 언어별로 추천 라이브러리와 지원상태를 한눈에 볼 수 있어서 믿고 쓸 수 있고 JWT에 대한 보안 취약점도 잘 보고되고 있다.
마침표를 구분자로 세 부분으로 나뉘어져있고 header에는 타입과 jwt signing 알고리즘(이 방법으로 토큰을 생성하겠다), 페이로드에는 담아서 전달하고 싶은 데이터가 들어있고 마지막으로 세트가 변조되었는지 확인하는 부분인 Signature이 있다.
JOSE 헤더와 JWT Claim Set를 base64로 인코딩해서 만든 두 값을 마침표(.)로 이어 붙이고 지정한 알고리즘 HS256으로 인코딩한 것이 JWT 토큰의 세 번째 부분인 Signature이다. 출처 : 아웃사이더의 데브 스토리
헤더와 JWT Claim Set은 암호화를 한 것이 아니라 단순히 JSON문자열을 base64로 인코딩한 것뿐이다. 그래서 누구나 이 값을 다시 디코딩하면 JSON에 어떤 내용이 들어있는지 확인할 수 있다. base64를 디코딩해보고 싶을 때는 아래에서 해봐도 된다. https://www.base64decode.org/
stateless vs stateful
서버가 클라이언트(사용자)의 정보를 저장하느냐 아니냐에 따라 stateful(상태유지)와 stateless(무상태)로 나눌 수 있다. 이 블로그의 자전거를 구매하는 대화의 예시를 통해 무상태와 상태유지의 차이를 쉽게 알 수 있다!
예를 들어서 stateless(무상태)는 서버가 아무것도 기억하지 않고 stateful(상태유지)는 클라이언트가 전에 했던 요청을 서버가 다 기억하고 있는 것이다. 무상태는 클라이언트의 요청으로 더 많은 데이터를 소모하지만 서버의 확장성이 높다. 상대적으로 특별한 일이 없다면 무상태를 지향해야한다.
HTTP는 statelss, connectless 즉 클라이언트의 요청을 저장하지 않고 연결된 상태로 있지 않는다. 무상태를 디폴트로 로그인을 유지하는 등의 기능이 필요할 때 쿠키와 세션을 사용한다.
Cookie, Session
헨젤과 그레텔이 쿠키를 조금씩 떨어뜨려서 길을 찾았다는 이야기로부터 이름을 따온 쿠키는 클라이언트에 저장된 사용자의 정보를 의미한다. 크롬의 자물쇠 표시를 누르면 저장된 쿠키들을 볼 수 있다. + 크롬의 시크릿모드는 창을 끄는 순간 쿠키가 지워진다.
2000년대 초반까지도 쿠키정보가 암호화되지 않아서 해킹에 취약했다고 한다. 그래서 생겨난 것이 정보를 웹 서버에 저장하는 세션이다. + 브라우저가 종료되면 세션도 만료된다.
쿠키
- 이름, 값, 만료일(저장 기간 설정), 경로 정보로 구성되어 있다.
- 클라이언트에 총 300개의 쿠키를 저장할 수 있다, 도메인 당 20개의 쿠키를 가질 수 있다
- 하나의 쿠키는 4KB(=4096byte)까지 저장 가능하다.
쿠키 사용 예시로는 방문했던 사이트에 다시 방문 하였을 때 아이디와 비밀번호 자동 입력, “오늘 이 창을 다시 보지 않기” 체크가 있다.
브라우저가 Request를 보낼 때 함께 보내는 쿠키의 값들은 장고에서 request.COOKIE 객체를 통해 접근 가능하다.
쿠키와 세션의 작동 방식
세션
- 웹 서버에 웹 컨테이너의 상태를 유지하기 위한 정보를 디비나 메모리에 저장한다.
- 브라우저를 닫거나, 서버에서 세션을 삭제했을때만 삭제가 되므로 쿠키보다 보안이 좋다. 서버의 리소스를 쓰므로 비용이 있다.
- 각 클라이언트 고유 Session ID를 부여한다, Session ID로 클라이언트를 구분하여 각 클라이언트 요구에 맞는 서비스 제공한다.
화면이 이동해도 로그인이 풀리지 않고 로그아웃하기 전까지 유지하는 것이 세션의 예시이다.
해당 브라우저와의 연결 상태를 기억하고 있는 세션 저장소는 장고에서 request.session객체를 통해 접근 가능하다. 장고에 SessionMiddleware라는 기본 미들웨어가 있는데 이는 클라이언트가 보내는 요청마다 request.session 속성에 이미 생성되어있는 세션 객체를 연결해준다. 세션이 생성되어있지 않은 상태라면 빈 껍데기를 연결해주고 session_key필드값이 None이다. 장고의 세션 프레임워크 튜토리얼
장고의 세션엔진 대용으로 Redis를 사용할 수 있고 Redis를 사용하면 성능이 향상된다. 보통 장고에서는 memcached 등을 사용해서 캐시를 설정하는데 Redis를 사용할 경우 memcached를 사용했을 때와 비교하여 손색이 없을정도라고 한다.
Redis
한 마디로 메모리를 이용하여 고속으로 <key, value> 스타일의 데이터를 저장하고 불러올 수 있는 시스템이라고 할 수 있다. 2009년 살바토르 산필리포가 개발한 오픈 소스 기반 비관계형 데이터베이스 관리 시스템으로 데이터베이스를 쓸 때 입출력에 시간이 걸리기 때문에 메모리 기반의 저장소에 캐시처럼 정보를 저장해놓고 빠르게 입출력할 수 있도록 만든 것이다.
Redis를 이용하면 MySQL보다 10배 빠를 수 있지만 메모리는 휘발성이기때문에 시스템이 꺼지면 모든 데이터는 날아가므로 Redis는 임시 데이터를 저장하는데 사용한다. -> 09.07수정 Redis에서 비휘발성으로 설정할 수도 있다.redis를 장고 세션 저장으로 사용하기(영문)