Jason Web Token
Jason 또는 평문을 서명(JWS)하거나 암호화(JWE) 한다. [RFC7519]
이번 프로젝트에서는 패스워드를 SHA256으로 암호화해서 DB에 저장하고, 서명이 들어간 토큰을 클라이언트에게 전달하기 위해서 JWT를 사용했습니다.
Flask에 JWT를 사용했습니다. PyJWT를 설치했습니다.
로그인이 성공한 경우 저장된 쿠키(토큰)
//JWT import
import jwt
//JWT 서명 과정에서 키를 사용합니다. 키 값은 자유롭게 지정할 수 있습니다.
SECRET_KEY = 'SPARTA'
//로그인은 POST 방식으로 진행합니다.
<클라이언트>
$.ajax({
type: "POST",
url: "/sign_in",
data: {
//username을 user_give로 전달
username_give: username,
//password를 password_give로 전달
password_give: password
},
success: function (response) {
//로그인이 성공했다면
if (response['result'] == 'success') {
//쿠키에 토큰값을 mytoken으로 저장
$.cookie('mytoken', response['token'], {path: '/'});
//쿠키 저장하고 메인 페이지로 이동
window.location.replace("/")
} else {
//성공하지 못하면 서버에서 받은 경고 메세지 출력
alert(response['msg'])
}
}
});
<서버>
@app.route('/sign_in', methods=['POST'])
def sign_in():
# 클라이언트에서 보낸 아이디, 패스워드 저장
username_receive = request.form['username_give']
password_receive = request.form['password_give']
# 저장한 패스워드를 SHA256으로 암호화해서 해쉬 값으로 저장 암호화한 패스워드는 복호화가 불가능한듯
pw_hash = hashlib.sha256(password_receive.encode('utf-8')).hexdigest()
# db에 아이디와 패스워드 해쉬 값 조회
result = db.users.find_one({'username': username_receive, 'password': pw_hash})
# 조회 결과가 none이 아니라면(조회결과가 있다는 이야기고 따라서 로그인이 가능하다)
if result is not None:
# payload에 아이디와 토큰 유효시간 저장
payload = {
'id': username_receive,
'exp': datetime.utcnow() + timedelta(seconds=60 * 60 * 24) # 로그인 24시간 유지
}
# AWS에서는 .decode('utf-8')이 있어야 에러나 나지 않습니다.
# 패이로드를 키로 암호화해서 토큰에 저장, 이는 키로 복호화가 가능한 듯, encoding과 encryption은 다르며 JWT는 서명용이라는 글이 있다
token = jwt.encode(payload, SECRET_KEY, algorithm='HS256').decode('utf-8')
# 성공 결과와 토큰 정보를 클라이언트에 리턴 합니다.
return jsonify({'result': 'success', 'token': token})
# 찾지 못하면
else:
return jsonify({'result': 'fail', 'msg': '아이디/비밀번호가 일치하지 않습니다.'})
//페이지 이동 시 토큰 정보를 확인하는 예시
@app.route('/')
def home():
//클라이언트의 토큰(mytoken) 정보를 받습니다.
token_receive = request.cookies.get('mytoken')
try:
//토큰의 payload를 키로 decode 합니다.
payload = jwt.decode(token_receive, SECRET_KEY, algorithms=['HS256'])
//payload의 id를 user_id에 저장합니다.
user_id = payload['id'];
//DB에서 ID를 검색합니다.
review_list = list(db.reviewlist.find({}, {'_id': False}));
//이 예시는 인증에 성공한 경우 reviewList.html로 이동합니다.
return render_template('reviewList.html', reviewList=review_list, userid=user_id)
//유효시간이 만료된 경우
except jwt.ExpiredSignatureError:
return redirect(url_for("login", msg="로그인 시간이 만료되었습니다."))
//ID정보를 DB에서 찾지 못한경우
except jwt.exceptions.DecodeError:
return redirect(url_for("login", msg="로그인 정보가 존재하지 않습니다."))
//토큰의 유효기간이 남아있을 때 로그아웃
function logout() {
//쿠키를 삭제합니다. 이 예시에서 쿠키는 mytoken이라는 이름으로 저장되어있습니다.
$.removeCookie('mytoken');
alert('로그아웃 완료!');
window.location.href = '/login';
}
RFC7519: https://datatracker.ietf.org/doc/html/rfc7519
rfc7519
datatracker.ietf.org