로그인한 유저가 추가적인 기능을 이용할 때, 해당 사용자가 맞는지 확인이 필요합니다. 로그인한 유저들에게 매번 재로그인을 시킬 수는 없습니다. 그렇다면 어떤 방식으로 확인할 수 있을까요?
서버측에서 유저들의 정보를 기억하는 방식입니다. 여기서 세션이라는 용어가 나오는데, 세션은 클라이언트의 데이터를 서버 측에 저장하는 것을 말합니다.
세션을 사용한 인증과정을 보겠습니다.

유저가 로그인에 성공하면, 세션이 서버에 저장됩니다. 식별을 위해 SessionId 를 반환하고 브라우저는 쿠키에 SessionId 를 저장합니다.
서버에 보내는 요청에는 쿠키가 들어있고, 서버는 클라이언트가 보낸 SessionId와 저장하고 있는 세션을 비교하고 응답을 보내줍니다.
세션을 기반으로 인증을 진행한다면 유저가 매번 재로그인을 할 필요가 없습니다. 이렇게 좋아보이는 세션기반 인증에는 몇 가지 단점이 있습니다.
2번 경우처럼 다양한 플랫폼에서 인증을 진행하려면 어떠한 방식을 써야할까요 ?
유저의 인증정보를 서버에 보관하지 않고, 클라이언트에 보관하면 어떨까요 ?
JWT(JSON Web Token)는 JSON 객체를 사용하여 가볍고 자가수용적인 방식으로 정보를 안전성있게 전달할 수 있는 구조입니다.
JWT 는 헤더, 페이로드, 서명으로 이루어져 있습니다.
헤더에는 토큰 타입과 해시 알고리즘 정보가 들어갑니다.
페이로드에는 토큰에 담을 정보(ex user_name, user_id)가 들어갑니다.
서명부분은 헤더의 해시 알고리즘을 사용하는 곳입니다.

작성한 헤더와 페이로드는 인코딩만 이루어지고, 서명부분은 암호화가 이루어집니다. 만들어진 JWT 구조는 헤더.페이로드.서명 형태입니다.
보셨다시피 JWT 는 페이로드를 숨기지 않습니다. 그렇기에 페이로드에는 민감한 정보를 담아서는 안 됩니다. JWT 의 목적은 데이터가 인증된 소스에서 생성되었다는 것을 증명하는 것입니다.
JWT 를 이용한 인증과정입니다.

JWT 는 자체에 인증정보를 포함하고 있기에 서버에 따로 저장할 필요가 없습니다. 정보를 저장하지 않으므로 이용자 요청에 대해 리소스를 절약할 수 있습니다.
또한 토큰을 클라이언트 측에 저장되기 때문에 다양한 플랫폼에서 사용할 수 있습니다.
파이썬에서 jwt 를 적용해보겠습니다 .
>>> import jwt
>>> secret = "kmc"
>>> access_token = jwt.encode({"user":"mincheol"},secret,algorithm="HS256")
>>> access_token
b'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoibWluY2hlb2wifQ.NRRQMekvmxsxilUc8BemQ2bCYkjq3daSnfQ6OmxAHIs'
# secret 키를 설정해주지 않으니 에러가 발생
>>> access_token2= jwt.encode({"user":"mincheol"},algorithm="HS256")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: encode() missing 1 required positional argument: 'key'
# 생성된 토큰을 디코드
>>> original = jwt.decode(access_token,secret,algorithm="HS256")
>>> original
{'user': 'mincheol'}
# 다른 secret 키를 사용하니 에러가 발생
>>> original_2 = jwt.decode(access_token,"mincheol",algorithm="HS256")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/jwt/api_jwt.py", line 91, in decode
decoded = super(PyJWT, self).decode(
File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/jwt/api_jws.py", line 155, in decode
self._verify_signature(payload, signing_input, header, signature,
File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/jwt/api_jws.py", line 223, in _verify_signature
raise InvalidSignatureError('Signature verification failed')
jwt.exceptions.InvalidSignatureError: Signature verification failed
이번 프로젝트에서 로그인 부분을 맡았기에 JWT 를 적용했습니다 .
class SignInView(View):
...
if bcrypt.checkpw(data['password'].encode('utf-8'), user.password.encode('utf-8')):
access_token = jwt.encode({'id': user.id, 'exp': datetime.utcnow() + timedelta(hours=24)}, SECRET,
algorithm=ALGORITHM)
return JsonResponse({'ACCESS_TOKEN': access_token}, status=201)
bcrypt 라이브러리를 통해 db 에 저장된 암호와 입력받은 암호를 비교해줍니다. 두 암호가 같다면 유효시간을 설정한 JWT 토큰을 전달해주고 로그인을 성공시킵니다.
유저가 여러 기능을 이용할때, @id_auth 데코레이터를 이용해 JWT 토큰의 유효성을 검사했습니다.
def id_auth(func):
def wrapper(self, request, *args, **kwargs):
try:
access_token = request.headers.get('Authorization', None)
payload = jwt.decode(access_token, SECRET, algorithms = ALGORITHM)
user = User.objects.get(id = payload["id"])
request.user = user
return func(self, request, *args, **kwargs)
except jwt.DecodeError:
return JsonResponse({'MESSAGE': 'INVALID_TOKEN'}, status=401)
except jwt.InvalidTokenError:
return JsonResponse({'MESSAGE': 'INVALID_ACCESS_TOKEN'}, status=401)
except jwt.ExpiredSignatureError:
return JsonResponse({'message': 'EXPIRED_TOKEN'}, status=401)
except User.DoesNotExist:
return JsonResponse({'MESSAGE': 'INVALID_USER'}, status=401)
return wrapper
스마트폰 이후, 여러 플랫폼에서 더 적합한 방법은 세션 인증보다 JWT 인증이라고 할 수 있습니다. 하지만 JWT 인증이 최고는 아니며, 구현방법에 따라 서버 기반 세션인증을 사용할 수도 있습니다.
참고
https://bcho.tistory.com/999
https://velopert.com/2389
https://velopert.com/2389
https://pyjwt.readthedocs.io/en/stable/index.html