# 토큰을 쓰는 이유가 무엇일까?
우리가 이용하는 서비스의 대다수는 "로그인"시스템이 존재한다. 로그인을 한 후 접근가능한 사이트에서 또는 서비스를 이용하게 된다. 그렇다면 로그인을 마치고 다음으로 이동하는 사이트에서 우리가 로그인 한 사용자라는 것은 어떻게 알 것인가? 여기에서는 대표적으로 세션 방식과 토큰을 이용하는 방식이 이용된다고 한다. 토큰을 다뤄볼 것이기 때문에 토큰에 대한 짧은 설명을 하도록 하겠다. 물론 내가 이해한 것이 베이스다.
로그인을 수행 한 후, 우린 유저의 브라우저에 토큰을 발급해주는데 그 토큰은 쿠키에 저장 돼 있어, 같은 도메인의 사이트라면 페이지를 이동하더라도 쿠키에 들어있는 데이터들은 항상 같이 이동하게 된다. 거기에 토큰이 존재하기 되는데, 클라이언트(브라우저)가 보내주는 토큰을 서버에서는 내가 지정한 secretkey(암호화 키)를 바탕으로 토큰이 올바른지 해석하게 된다. 그 중 JWT라는 방식이 있고 널리 쓰이는 기술이기때문에 장고에서 구현해보고자 한다.
세션을 활용하는 로그인의 장점은 데이터베이스에 현재 로그인세션을 기록해두기 때문에, 실시간으로 어떤 유저가 로그인 되고 있는지 판단 할 수 있으며, 그로인해 다중로그인 방지나 로그인된 사용자를 강제로 종료 시킬 수 있다는 장점이 있다고 한다.
어떤게 좋고 나쁘다고 평가할수는 없고 쓰임에따라서 사용하면 된다. 간단한 프로젝트에는 JWT방식이 많이 쓰인다고 한다.
네이버가 어떤 로그인방식을 사용하는지는 모르겠지만, 네이버에 로그인 후 쿠키를 지울경우 로그아웃 상태가 된다.
이렇듯 사실 로그인유지 기능은 항상 해당 유저가 로그인 돼 있다는 사실을 매 redirect마다 서버에 전송하는 것이다.
# 사용되는 라이브러리
- django-rest-framework
- Pyjwt
- django-cors-headers
Pyjwt는 jwt를 쉽게 생성해줄 수 있게 하는 파이썬 라이브러리다. django-cors-headers는 cors정책 즉 다른 포트에서 접근할 경우 보안상 접속을 못하게 하지만 그것을 할 수 있게 해준다. 즉 포트번호가 다를경우에도 접속이 가능하다.
# 흐름
views.py에 총 4개의 뷰를 만들 것이다. register(회원가입), login(로그인), logout(로그아웃), user(로그인 시 접속가능).
회원가입을 한 후, 유저가 로그인에 성공할 경우 토큰을 쿠키에 담아 response하고 해당 토큰을 가지고 있으면 user에 접속할 수 있다. 로그아웃시에는 쿠키에서 해당 토큰일 지워버린다.
그렇다면 로그아웃을 하지 않고 브라우저를 끈다면 어떻게 될까? 영원히 로그인상태가 지속될거라 생각하지만 토큰에는 유효시간을 부여하여 정해진 시간이 끝나면 해당 토큰은 유효하지 않은 토큰이 된다.
# 구현해보기
## 회원가입
from rest_framework.decorators import APIView
from rest_framework.renderers import JSONRenderer
from rest_framework.response import Response
from rest_framework.exceptions import AuthenticationFailed
from accounts.serializers import UserSerializer
from .models import User
# install PyJWT
import jwt,datetime
#회원가입
class RegisterView(APIView) :
def post(self, req):
serializer = UserSerializer(data=req.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data)
회원가입에서는 내 DB에 유저정보를 등록해주기만 하면 된다.
## 로그인
class LoginView(APIView) :
def post(self,req):
email = req.data['email']
password = req.data['password']
# email is unique,
user = User.objects.filter(email=email).first()
serialize_user = UserSerializer(user)
json_user = JSONRenderer().render(serialize_user.data)
if user is None :
raise AuthenticationFailed('User does not found!')
# is same?
if not user.check_password(password) :
raise AuthenticationFailed("Incorrect password!")
## JWT 구현 부분
payload = {
'id' : user.id,
'exp' : datetime.datetime.now() + datetime.timedelta(minutes=60),
'iat' : datetime.datetime.now()
}
token = jwt.encode(payload,"secretJWTkey",algorithm="HS256")
res = Response()
res.set_cookie(key='jwt', value=token, httponly=True)
res.data = {
'jwt' : token
}
return res
JWT 구현 부분이라고 주석을 처리한 곳 전까지는 전송받은 email과 passowrd과 DB에 있는것과 일치하는지 확인하는 과정이다.
import jwt,datetime
## JWT 구현 부분
payload = {
'id' : user.id,
'exp' : datetime.datetime.now() + datetime.timedelta(minutes=60),
'iat' : datetime.datetime.now()
}
token = jwt.encode(payload,"secretJWTkey",algorithm="HS256")
res = Response()
res.set_cookie(key='jwt', value=token, httponly=True)
res.data = {
'jwt' : token
}
return res
JWT는 아래 첨부한 이미지처럼 구성되는 payload에는 우리가 담을 데이터를 넣어주게 된다. 여기서 로그인 사용자의 고유한 식별값인 id를 통해서 해당유저의 정보를기억할 수 있게 한다.
exp는 토큰의 만료시간을 지정해준다.
iat는 토큰의 생성시간을 지정해준다.
JWT에 관한 자세한 설명은 벨로퍼트님의 게시글을 참고하면 된다.
이렇게 만든 토큰을 쿠키에 저장해서 return하여 response로 클라이언트가 받게 해주면 쿠키를 전해주는 일까지 성공이다.
## 로그인 유지 (로그인 여부 확인)
class UserView(APIView) :
def get(self,req):
token = req.COOKIES.get('jwt')
if not token :
raise AuthenticationFailed('UnAuthenticated!')
try :
payload = jwt.decode(token,'secretJWTkey',algorithms=['HS256'])
except jwt.ExpiredSignatureError:
raise AuthenticationFailed('UnAuthenticated!')
user = User.objects.filter(id=payload['id']).first()
serializer = UserSerializer(user)
return Response(serializer.data)
로그인시 전해줬던 쿠키를 확인하여 해당 코드를 decode하게 되는데, 내가 지정했던 암호화키와 algorithms방식을 지정해주면 된다. 그렇게 decode에 성공하면 jwt토큰을 만들때 payload에 담아두었던 user.id속성을 이용하여 DB에 접근하여 원하는 정보를 빼오면 된다.
## 로그아웃
class LogoutView(APIView) :
def post(self,req):
res = Response()
res.delete_cookie('jwt')
res.data = {
"message" : 'success'
}
return res
로그아웃은 메우 간단하다. 쿠키에 들어있는 토큰을 지워주기만 하면된다.
JWT를 구현해보기 위해 수많은 구글링을 했지만 위 방법이 가장 쉽고 직관적인거 같아 기록해둔다. JWT#1편고 이어진다. #1에도 첨부했듯이 해외 유튜버 분의 자료를 보면서 구현했다. 온통 영어라 들리는대로만 이해하면서 구현했다.
생각보다 어렵지 않았다. 이제 클라이언트에서 항상 API서버에 접속할때마다 로그인 여부를 묻고 싶은데, 그럴경우에는 UserView에서 작성했던 코드를 매번 작성해야하는지가 궁금하다. 그것은 또 찾아보면서 알아가도록 하곘다. 물론 함수나 모듈로 만들어서 간단하게 사용할 수 있겠냐고 묻겠지만, 그럴경우에도 구현하는 모든 API들에 해당 모듈을 적어줘야할까 의문이다.
이제 세션을 통해 로그인을 유지하는 방법에 대해서도 궁금하다. 그 방법에 대해서도 시간이난다면 공부해보고 기록하도록 하겠다.