[JWT] 인증과 인가 그리고 JWT에 대해 알아보기

11691 단어 JWTJWT

인증(authentication): 로그인, 아이디와 패스워드를 통해 회원임을 인증 받는 것

인가(autherization): 회원으로만 할 수 있는 행동을 할 때 서버가 회원임을 확인하고 행동을 인가하는 것

인가를 위해 매 요청마다 아이디와 비밀번호가 담겨서 날아다니면 보안상 위험하며 매번 비밀번호가 알고리즘으로 암호화된 계산값과 일치하는지 확인하는 작업을 거쳐야 해서 시간과 자원을 많이 사용하게 된다.

해결책

메모리를 사용한 세션/쿠키 방식

  • 장점
    • 메모리를 사용하면 비교적 빠르다
  • 단점
    • 용량이 작기 때문에 사용자가 동시에 많이 접속하면 메모리가 부족해짐
    • 서버가 꺼져버리면 데이터가 휘발 → 모든 사용자 로그인 튕김

서버를 사용한 세션/쿠키 방식

  • 장점
    • 용량이 크기 때문에 동시접속자가 많을 때 메모리 방식보다 안정적
  • 단점
    • 메모리 방식보다 느리다
    • 서버를 여러 대 둘 경우, 요청을 분산하는 로드밸런싱을 하는데 인증을 받은 서버와 이후 인가가 필요한 요청을 받은 서버가 다를 경우 세션 유지가 되지 않는다.
  • 보완방법
    • 인증 정보를 특정 데이터베이스용 서버에만 넣어 두는 방법(하지만 속도가 많이 느리다)
    • Redis, MemCached와 같은 메모리형 데이터베이스 서버를 사용한다.(메모리 방식보다 안정적이지만 역시 과부하 발생 가능성 존재)

JWT(Json Web Token)

특징

  • 사용자가 로그인을 하면 토큰을 전달해주며, 서버에는 그 정보를 따로 저장하지 않음(세션/쿠키 방식과 달리 stateless함)
  • 인코딩된 3가지 데이터를 이어 붙임(xxxx.yyyyy.zzzzz~)
  • 이 3가지는 각각 1. 헤더 2. 페이로드 3. 서명으로 구분됨
  • 2번 페이로드를 디코딩하면 json 형식으로 여러 정보들이 들어있음
    • 이 안에는 토큰을 누가 누구에게 발급했는지, 언제까지 유효한지, 서비스가 사용자에게 이 토큰을 통해 공개하기 원하는 내용을 담을 수 있음(클레임)
  • 1번 헤더 부분은 type 즉 토큰의 타입인 JWT가 들어가 있음(고정값)
    • 다음으로는 alg(알고리즘), 즉 3번 서명값을 만드는데 사용되는 알고리즘이 지정되며 이 알고리즘으로 만든 값은 복호화가 불가능(HS256 등)
  • 1번 헤더와 2번 페이로드 그리고 ‘서버에 감춰놓은 비밀값’ 총 3가지를 암호화 알고리즘에 넣고 돌리면 3번 서명값이 나오게 된다.

인가 과정

  • 서버는 요청이 들어오면 token의 1, 2번 값을 서버의 비밀키와 함께 돌려 3번 서명값과 일치하는지 확인한다. 일치한다면 토큰 만료기간까지 확인 후에 인가를 하게됨

단점

  • stateful한 세션/쿠키 방식은 기억하는 대상의 상태를 언제든 제어가 가능하다. 예를들어 한 기기에서만 로그인이 가능하도록 한다면 새로운 기기에서 로그인 시 이전 로그인 세션 종료가 가능하다.
  • 하지만 JWT는 이러한 컨트롤이 불가능하다. 만약 토큰이 탈취당하면 토큰 만료 전까지 토큰 무효화 할 수 없다.

Refresh Token

  • 위 단점을 보완하기 위한 방법으로 로그인 후 access(짧은 수명), refresh(긴 수명) 2개의 토큰을 준다.
  • 이때 refresh 토큰은 DB에도 저장하게 된다.(stateful)
  • 유저는 access 토큰이 만료되면 refresh 토큰을 서버에 보내고 refresh 토큰 대조/토큰이 만료되지 않았으면 서버는 새로운 acceess 토큰을 유저에거 발급한다.
  • refresh 토큰을 사용할 경우 해킹이 감지되거나 했을 때(수상한 아이피, 사용자의 신고 등) 서버가 어떤 조치를 취할(refresh 토큰을 강제 만료) 수 있음

Django로 JWT 확인하기

  • django에서 JWT access token을 발급받아 이를 JWT 공식 사이트에서 decode 해보았다.

    • 먼저 header에는 token type과 alg가 들어있는 것을 확인할 수 있다.
    • 다음 payload에는 클레임들을 확인할 수 있다.
      • exp: 토큰 만료 시간
      • iat: 토큰 발급 시간
      • jti: 중복 방지를 위한 JWT 고유 식별자
    • jti 이후 클레임들은 서버에서 직접 토큰에 담은 유저 정보와 관련된 클레임들이다.
     #serializers.py
    from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
    
     class SellerTokenObtainPairSerializer(TokenObtainPairSerializer):
          @classmethod
          def get_token(cls, user):
              if user.is_staff == True:
                  token = super().get_token(user)
                  token['username'] = user.username
                  token['email'] = user.email
                  token['is_staff'] = user.is_staff
                  return token
    • django에서 위 코드를 통해 유저에게 JWT 토큰을 반환할 때 username, email, is_staff 정보가 추가로 payload에 담기게 되며 이외의 정보들을 추가할 수 있다.


    • 마지막 서명에서는 헤더, 페이로드, 비밀키를 암호화 했음을 알 수 있다.


다음은 django에서 JWT 관한 설정들이다.


 #settings.py
'''
REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': (
       'rest_framework.permissions.IsAuthenticated',
    ),
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_simplejwt.authentication.JWTAuthentication',
        'rest_framework.authentication.BasicAuthentication',
        'rest_framework.authentication.SessionAuthentication',
    ),
} 
'''
  SIMPLE_JWT = {
      'ACCESS_TOKEN_LIFETIME': timedelta(minutes=30),
      'REFRESH_TOKEN_LIFETIME': timedelta(days=14),
      'ROTATE_REFRESH_TOKENS': True,
      'BLACKLIST_AFTER_ROTATION': True,
      'UPDATE_LAST_LOGIN': False,

      'ALGORITHM': 'HS256',
      'SIGNING_KEY': SECRET_KEY,
      'VERIFYING_KEY': None,
      'AUDIENCE': None,
      'ISSUER': None,
      'JWK_URL': None,
      'LEEWAY': 0,

      'AUTH_HEADER_TYPES': ('Bearer',),
      'AUTH_HEADER_NAME': 'HTTP_AUTHORIZATION',
      'USER_ID_FIELD': 'id',
      'USER_ID_CLAIM': 'user_id',
      'USER_AUTHENTICATION_RULE': 'rest_framework_simplejwt.authentication.default_user_authentication_rule',

      'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',),
      'TOKEN_TYPE_CLAIM': 'token_type',
      'TOKEN_USER_CLASS': 'rest_framework_simplejwt.models.TokenUser',

      'JTI_CLAIM': 'jti',

      'SLIDING_TOKEN_REFRESH_EXP_CLAIM': 'refresh_exp',
      'SLIDING_TOKEN_LIFETIME': timedelta(minutes=5),
      'SLIDING_TOKEN_REFRESH_LIFETIME': timedelta(days=1),
  }
  • 먼저 Django REST Framework에서 인가를 위해 JWT를 사용했기 때문에 REST Framework의 DEFAULT_AUTHENTICATION_CLASSESrest_framework_simplejwt.authentication.JWTAuthentication를 추가해주었다.

  • 다음으로 simple JWT 설정을 확인해보면,
    access token의 만료시간은 30분, refresh token의 만료기간은 2주로 설정된 것을 확인할 수 있다.

  • 또한 암호화 알고리즘은 'HS256'이며, 서명을 만드는데 사용된 비밀키는 django의 SECRET_KEY와 동일하게 설정해주었다.


    참고:https://www.youtube.com/watch?v=1QiOXWEbqYQ

좋은 웹페이지 즐겨찾기