JWT(Jason Web Token)
Json 객체에 인증에 필요한 정보들을 담아 비밀키로 서명한 토큰.
- 인터넷 표준 방식
- 인증 (Authentication), 인가(Authorization)에 사용.
JWT 사용의 예시를 나타내면 다음과 같다.
<로그인 이전>
<로그인 이후>
JWT 사용의 이유
JWT의 구조
JWT는 Json형식으로 위와 같은 구조를 가지고 있으며, 각각 다음과 같은 정보들을 가진다.
- Header: 알고리즘 방식 (alg), 토큰의 타입(typ)
- Payload: 토큰에서 사용할 정보
- Signature: 토큰 인코딩, 유효성 검증에 필요한 암호화 정보.
🚩 Python Flask에서 Flask-JWT-Extended를 사용해 JWT 토큰 실습
0. flask_jwt_extended 설치 및 초기 설정
pip install flask-jwt-extended
pip 환경에서 위와 같이 flask-jwt-extended를 설치 해준다.
from flask_jwt_extended import *
app = Flask(__name__)
app.config['JWT_TOKEN_LOCATION'] = ['cookies'] # 쿠키에서 JWT를 찾도록 설정
app.config['JWT_ACCESS_COOKIE_NAME'] = 'access_token'
app.config.update(
JWT_SECRET_KEY = "TEST"
)
위와 같이 JWTManager의 초기설정을 해 두었다.
* 'JWT_TOKEN_LOCATION'을 따로 설정해주지 않으면, flask_jwt_extended에서는 JWT를 찾아오거나 발급할 때 기본적으로 HTTP의 Authorization 헤더를 사용한다.
나는 JWT Token을 쿠키 스토리지에 저장하고 사용할 것이기 때문에, 위와 같이 설정하였다.
1. 로그인 구현
app.py
from flask import Flask, jsonify, request, render_template, url_for, redirect
from pymongo import MongoClient
from flask_jwt_extended import *
1
client = MongoClient('localhost', 27017)
db = client.users
jwt = JWTManager(app)
# 로그인 기능
@app.route('/api/signin', methods=['POST'])
def login():
userId = request.form['id']
userPw = request.form['pw']
# DB에 있는 데이터와 비교하기
user = db.userInfo.find_one({'id' : userId})
if user:
if not verify_password(user['pw'], userPw):
return jsonify({'result' : 'failed', 'msg' : '로그인이 실패했습니다.'})
# JWT 토큰 생성 (유효시간 60분)
expires = datetime.timedelta(minutes=60)
access_token = create_access_token(
identity = userId,
expires_delta = expires,
additional_claims={
'name': user['nickName'],
'introduced': user['hasIntroduce'],
'profile_image':user.get('profile','')
}
)
response = jsonify({'result' : 'success', 'msg' : '정상적으로 로그인이 되었습니다.', "token" : access_token})
response.set_cookie('access_token', access_token, secure=True, samesite='Lax') # 쿠키 보안 설정
return response
else:
return jsonify({'result' : 'failed', 'msg' : '로그인이 실패했습니다.'})
위에서 실습한 코드를 아래에서 차근차근 살펴보도록 하자.
정상적으로 로그인이 된 경우, 다음과 같이 jwt 토큰을 발급한다.
# JWT 토큰 생성 (유효시간 60분)
expires = datetime.timedelta(minutes=60)
access_token = create_access_token(
identity = userId,
expires_delta = expires,
additional_claims={
'name': user['nickName'],
'introduced': user['hasIntroduce'],
'profile_image':user.get('profile','')
}
)
response = jsonify({'result' : 'success', 'msg' : '정상적으로 로그인이 되었습니다.', "token" : access_token})
response.set_cookie('access_token', access_token, secure=True, samesite='Lax') # 쿠키 보안 설정
return response
expires
는 토큰의 유효 시간으로, 코드에서는 60분을 설정했다. 유효한 시간이 지나면 토큰이 자동으로 만료되며, 페이지에서는 로그아웃 된다.additional_claims
에 해당 정보들을 추가했다.response.set_cookie
쿠키 스토리지에 해당 토큰을 저장하도록 했다. signin.html
function submitSignIn() { //로그인 버튼 눌렀을 때
let id_give = $('#input-id').val();
let pw_give = $('#input-pw').val();
//서버 API로 전달.
$.ajax({
type: "POST",
url: "/api/signin",
data: {id: id_give, pw: pw_give},
success: function (response) {
if (response["result"] != "success"){
alert(response["msg"]);
}
else {
alert(response["msg"]);
window.location.href = '/';
}
}
})
}
2. 로그인 이후 사용 :: 검증
app.py
@app.route('/')
#jwt_token을 확인한다.
@jwt_required(optional=True)
def home():
logged_in = False
current_identity = None
name = None
is_introduced = None
#get_jwt()를 통해 토큰을 쿠키에서 꺼내 jwt_check에 할당한다.
jwt_check = get_jwt()
if jwt_check:
#jwt 토큰이 있는 경우, 값들을 꺼내 다음과 같이 할당한다.
logged_in = True
current_identity = jwt_check["sub"]
name = jwt_check["name"]
is_introduced = jwt_check["introduced"]
# 템플릿에 값들을 함께 전달한다.
return render_template('index.html',
logged_in=logged_in,
name=name,
is_introduced=is_introduced,
current_identity=current_identity)
로그인 이후 사용되는 페이지 (즉, 인가가 필요한 페이지)에 대해
@jwt_required()
를 붙여주어 jwt 토큰을 확인할 수 있다.
이 때, @jwt_required(optional=True)
처럼 작성해주면, jwt 토큰이 없어도 일단 페이지에 접근할 수 있다.
참고: 값들을 json으로 묶어서 script로 전달할 수도 있다. https://karl27.tistory.com/105 (이 방법이 보기에 더 깔끔해보인다. 다음에 한 번 시도해보려 한다.)
render_template()은 SSR(서버 사이드 렌더링) 방식으로 템플릿을 렌더링 해 호출한다. 이에 대해서는 CSR과 SSR에 대해 한 번 더 포스팅하려 한다.
index.html
<script>
const loggedIn = "{{ logged_in }}";
const name = "{{ name }}"; // 문자열인 경우 따옴표로 감싸기
const isIntroduced = "{{is_introduced}}";
const currentIdentity = "{{ current_identity }}";
// 이제 JavaScript에서 사용할 수 있음
console.log("Logged in:", loggedIn);
console.log("Name:", name);
console.log("Is Introduced:", isIntroduced);
console.log("Current Identity:", currentIdentity);
</script>
위처럼 render_template()을 통해 전달받은 값을 JavaScript에서 선언 해 이용해줄 수 있다.
✅ JWT TOKEN 직접 확인하기
https://jwt.io/ 에 들어가 token 값을 입력하면 현재 자신이 사용 중인 token이 어떤 구조로 이루어졌는지 쉽게 확인할 수 있다.
Chrome 브라우저를 통해 실습하는 경우, F12 (개발자 도구)
에서 Application - Cookies
의 쿠키 스토리지를 확인하면 본인의 token을 확인할 수 있다.