header, payload, signature 3부분으로 구성된다. (점으로 구분)
header : 토큰의 타입, 해시 암호화 알고리즘
payload : 정보를 담는 공간(name : value 한 쌍으로 이루어짐)
payload에 있는 속성들을 Claim Set이라고 부른다. (클라이언트와 서버 간의 주고받는 값들로 구성됨)
signature : 점을 구분자로 하여 header와 payload를 합친 문자열을 서명한 값
header에 정의된 알고리즘 방식으로 SECRET_KEY를 이용하여 Base64 URL-Safe로 인코딩한다.
header, payload 값은 인코딩 될 뿐이지 따로 암호화 되지 않기 때문에 중요한 정보는 들어가면 쉽게 노출될 수 있다.
하지만 signature의 경우 SECRET_KEY를 알지 못하면 복호화 할 수 없다.
해쉬 암호화 방식을 사용하기 때문에 암호문을 평문으로 바꿀 수 없다. 또한 해쉬 암호화를 진행하게 되면 단 한 글자만 바꿔도 암호문이 완전히 달라진다. 따라서 만약 payload의 정보를 의도적으로 변경하여 요청을 한다 해도 검증과정에서 들통나게 되어있다.
검증 방식은 받은 토큰의 header와 payload를 합친 문자열을 SECRET_KEY를 이용하여 암호화 한 값이랑 받은 토큰의 signature값이 같지 않으면 payload나 header의 정보가 바뀐것이므로 서버는 인가해주지 않는다.
사용자가 로그인을 하게 되면 서버로 로그인 정보(ID, PW)를 전송한다.
서버는 회원 정보가 담긴 DB를 조회하여 해당 User가 존재하는지 확인한다.
서버는 가입된 User임이 확인되면 "토큰"을 발급해준다.
사용자(클라이언트) 측에서는 받은 토큰을 저장해둔다.
데이터 요청 시, 서버한테 발급 받았던 토큰을 함께 보낸다.
서버는 토큰의 정보가 올바른지 검증을 시행한다.
검증이 잘 완료되면, 요청한 데이터를 보내준다.
=> 세션/쿠키 방식처럼 서버에 별도의 저장소가 필요없다!!
장점
단점
# 로그인 성공할때 토큰 생성
@app.route('/api/login', methods=['POST'])
def sign_in():
id_receive = request.form['username_give'] #사용자가 입력한 ID
pw_receive = request.form['password_give'] #사용자가 입력한 PW
result = db.userdb.find_one({'id': id_receive, 'pw': pw_receive}) #DB에서 사용자 찾기
if result is not None: #사용자가 존재한다면 토큰 발급
payload = { #JWT payload에 들어갈 부분을 name / value 형식으로 지정해주기
'id': id_receive, #사용자 ID를 payload에 담기
'exp': datetime.utcnow() + timedelta(seconds=60 * 60 * 24) # 유효기간 설정, 24시간으로 설정함.
}
token = jwt.encode(payload, SECRET_KEY, algorithm='HS256') # HASH-256 방식으로 암호화, 토큰 발급
return jsonify({'result': 'success', 'token': token}) # 사용자에게 토큰 전달
else: #사용자가 존재하지 않는다면 에러 메세지 응답
return jsonify({'result': 'fail', 'msg': '아이디/비밀번호가 일치하지 않습니다.'})
paylod - 이미 등록된 Claim 종류
@app.route('/')
def home():
token_receive = request.cookies.get('mytoken') #사용자에게서 토큰을 받음
try:
payload = jwt.decode(token_receive, SECRET_KEY, algorithms=['HS256']) #받은 토큰 검증
user_info = db.userdb.find_one({'id': payload['id']})
if user_info is not None:
postdata = list(db.cafelist.find({}, {'_id': False}))
return render_template("layout_postlist.html", postdata=postdata) #요청 데이터 응답
except jwt.ExpiredSignatureError: # 유효기간 만료 시 응답
return redirect(url_for("login", msg="로그인 시간이 만료되었습니다."))
except jwt.exceptions.DecodeError: # 페이로드 값이 다를 때의 응답
return redirect(url_for("login", msg="로그인 정보가 존재하지 않습니다."))