티스토리 뷰

반응형

0. 들어가면서

이 부분에서는 JWT를 쓰는 당위성에 대해 적어 보겠다. 코드만 보려면 긴 글을 읽지 않아도 된다.

 

일단 JWT를 왜 쓰는가에 대해 간단히 적어보겠다. 우리는 로그인, 로그아웃, 즉, 사용자의 인증정보를 관리하기 위해 검색을 하고 있는 것이다. 사용자 인증 정보는 세션 기반 인증과 토큰 기반 인증이 있는데, 세션 기반 인증은 유저가 로그인을 하면 서버 쪽에서 로그인 중이라고 기억을 하고 있는 것이다. 이렇게 되면 로그인 유저가 많아아 질 경우 성능에 무리가 간다. 그러면 토큰은 이러한 문제점이 없을까?

 

 토큰은 로그인 시 유저에게 토큰을 발급해준다. 즉, 서버쪽에서 저장을 하지 않기 때문에 서버 확장 시 매우 용이해진다. 왜냐하면 서버를 확장하여 분산되어 있어도 토큰을 서버에 요청을 하면 유저를 확인이 가능하게 된다. 추가적으로 페이스북이나 구글 계정을 통한 소셜 로그인도 토큰 기반 인증 시스템을 사용하므로 플랫폼 간 권한을 공유할 수 있다.

 

 토큰 발급 시 사용자에게 전달되는 방식은 크게 두 가지다.

방식 1.

로그인이 성공하면, 먼저 '응답 정보'에 토큰을 넣어서 전달한다. 그 이후 웹 요청 시에는 해당 값을 웹 스토리지(localStorage나 sessionStorage)에 넣어 두고 웹 요청 시마다 HTTP 헤더 값을 넣어서 요청을 한다. 장점은 구현이 쉽고 하나의 도메인에 제한되어 있지 않지만, XSS 해킹 공격으로 LocalStorage에 접근하면 토큰에 쉽게 접근 가능하다는 단점이 있다.

방식 2.

토큰을 쿠키에 넣어서 쿠키를 정보 전송수단으로 사용하는 것이다. (httpOnly 값을 활성화해주면 토큰이 자동으로 쿠키에 붙는다.) 이렇게 되면 자바스크립트로 토큰 값에 접근이 불가능하다. XSS의 위험을 방지하는 장점이 있지만, CSRF 공격(자동 탈퇴, 정보 변경)의 위험성이 생기게 된다. 그러나 CSRF는 보안에 신경을 쓰면 차단 가능하다.

 

 

 

 

 

2. JWT

 현재 backend에서 구현하고 있기 때문에, frontend에서의 구현은 나중에 다시 포스팅해 보겠다.

 

2.1 JWT정의

JWT(json web token)이란 로그인이 완료되었을 때 발행되는 토큰이다. 암호화된 회원정보가 들어가 있으면 복호화를 통해 사이트 내의 서비스를 사용할 수 있는지 확인(Authorizationathon)하는 데 사용한다.

 

2.2 JWT 구조

JWT는 헤더(header), 정보(payload), 서명(signature) 구조로 이루어져 있다.

  • header : 타입(JWT)과 알고리즘(alg)을 담는다.
  • payload : 보통 유저 정보와 만료기간의 객체형으로 담긴다. 즉, 토큰에 담을 데이터.
#payload에 들어갈 수 있는 클레임(딕셔너리의 키같은 느낌이다.)
iss: 토큰 발급자 (issuer)
sub: 토큰 제목 (subject)
aud: 토큰 대상자 (audience)
exp: 토큰의 만료시간 (expiraton), 시간은 NumericDate 형식으로 되어있어야 하며 (예: 1480849147370) 언제나 현재 시간보다 이후로 설정되어있어야합니다.
nbf: Not Before 를 의미하며, 토큰의 활성 날짜와 비슷한 개념입니다. 여기에도 NumericDate 형식으로 날짜를 지정하며, 이 날짜가 지나기 전까지는 토큰이 처리되지 않습니다.
iat: 토큰이 발급된 시간 (issued at), 이 값을 사용하여 토큰의 age 가 얼마나 되었는지 판단 할 수 있습니다.
jti: JWT의 고유 식별자로서, 주로 중복적인 처리를 방지하기 위하여 사용됩니다. 일회용 토큰에 사용하면 유용합니다.

# 위에 나타낸 내용만 클레임(키)값으로 적어야 하는 것은 아니다. 겹치지 않는 한에서 적어도 된다.
  • signature : header, payload를 인코딩 한 값을 합친 뒤 SECRET_KEY로 해쉬 한다. 즉, 검증하여 서버가 만든 토큰이 맞는지 확인하는 곳이다.
# Header ############################
{
    "alg": "HS256",
    "typ": "JWT"
}

# Payload ###########################
{
    "sub": "1234567890",
    "name": "John Doe",
    "iat": 1516239022
}

# Signature #########################
HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)

다시 말하면 위에 것들을 보내려면 아래처럼 암호화를 거쳐서 보내게 된다. 마침표(.)를 기준으로 Header.PayLoad.Signature 순서로 아래와 같이 암호와 되어 표현된다.

 

위의 내용인 Json 데이터를 encode(암호화) 하면 아래의 JWT(Json Web Token)이 된다.

반대로 아래의 JWT를 decode(복호화)하면 우리가 알아볼 수 있는 Json데이터가 된다.

ekejfELJFENLe5jefjEJHR3ejfhinel.ekjfh2eoiEHOEFInef3nowefn.EJW4KWENOnefwkl5nNELWFNn

즉, 해더(header).내용(payload).서명(signature)

 

 

3. 코드

3.1 설치

> pip install PyJWT

여기서 특이한 점은 설치는 PyJWT로 하고 import는 jwt로 한다.

 

 

3.2 import

import jwt

코드 구현 시 반드시 필요한 것은 SECRET_KEY와 ALGORITHM이다. 시크릿키는 장고의 프로젝트와 앱을 생성하면 자동으로 settings.py에 생성되는 키 값을 불러오면 된다. 그리고 ALGORITHM은 views.py에서 적을 예정이다.

 

코드 구현전에 흐름을 보면 

- 백엔드에서는 로그인 시 입력값이 DB의 회원정보와 일치하다면 JWT를 발급한다.

- 프런트엔드에서는 JWT를 받아서 로컬/세션 스토리지에 저장한다

- 유저가 회원 인증이 필요한 서비스에 접근했을 때, 프런트는 요청을 보낼 때 header, authorization에 토큰 값을 담아 보낸다.

- 백엔드에서는 받은 토큰값을 복호화(암호화의 반대말)하여 유저 정보를 확인한다.

- DB에서 해당 정보가 있다면, 해당 서비스를 이용할 수 있게 한다.

 

 

3.3 흐름도 구현

크게 구현해야 할 부분은 크게 두 부분으로 첫 번째는 로그인 시 토큰을 발급하는 부분과 두 번째는 토큰을 다시 받아서 검증하는 부분입니다.

토큰 발급

import jwt
import datetime

def create_token(github_token, email):
    encoded = jwt.encode({'exp': datetime.datetime.utcnow() + datetime.timedelta(seconds=300), "pk": user.pk}, settings.SECRET_KEY, algorithm="HS256"
    )
    return encoded

jwt.encode 함수의 첫 번째 함수는 payload이고 그 뒤로 secret이고 마지막은 해싱 알고리즘에 대한 내용이다. Pyload 부분을 보면 만료시간은 300초 뒤라는 걸 알 수 있다.

secret 부분은 settings.py에 있는 시크릿키를 불러오면된다. 세번째 알고리즘은 해싱 알고리즘인데, 무난한게 HS256이다.

 

토큰 검증

사용제가 제출한 token을 받아서 decode하는 거다.

jwt.decode(제출된토큰, SECRET, 해싱알고리즘) 

순서로 하면 된다.

 

+하나 참고할 것은, client에서 현재 가지고 있는 토큰이 만료되었는지 확인할 때는 secret이 필요하지 않습니다.

exp 값은 그냥 decode하는 것만으로도 알 수 있기 때문이다.

 


4. 마지막으로 간단 정리 코드

>>> import jwt

>>> encoded_jwt = jwt.encode({'some': 'payload'}, 'secret', algorithm='HS256')
>>> print(encoded_jwt)
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzb21lIjoicGF5bG9hZCJ9.4twFt5NiznN84AWoo1d7KO1T_yoc0Z6XOpOVswacPZg

>>> jwt.decode(encoded_jwt, 'secret', algorithms=['HS256'])
{'some': 'payload'}
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
글 보관함