web/back + front

vue+flask 시작 로그인 구현 3. flask jwt authorization

qkqhxla1 2018. 7. 19. 15:53

flask 에서는 jwt 인증이란걸 쓴다고 한다. 몇년전에 웹 공부할때 로그인 구현을 session과 쿠키에 넣어서 한적이 있었는데, 왜 굳이 flask에서는 jwt인증이란걸 쓰는지 모르겠다. 찾았더니 좋은 글이 나왔다.


https://stackoverflow.com/questions/43452896/authentication-jwt-usage-vs-session


JWT doesn't have a benefit over using "sessions" per say. JWTs provide a means of maintaining session state on the client in stead of doing it on the server.

What people often mean when asking this is "What are the benefits of using JWTs over using Server-side sessions"

....

Moving the session to the client means that you remove the dependency on a server-side session, but it imposes its own set of challenges.
- Storing the token securely
- transporting it securely
- JWTs Sessions can sometimes be hard to invalidate.
- Trusting the client's claim.

These issues are shared by JWTs and other client-side session mechanisms alike.

JWT in particular addresses the last of these. It may help to understand what a JWT is:

....

So in short: JWTs answers some of the questions and shortcomings of other session techniques.
1. "Cheaper" authentication because you can eliminate a DB round trip (or at least have a much smaller table to query!), which in turns enable horizontal scalability.
2. Tamper-proof client-side claims.

While JWTs does not answer the other issues like secure storage or transport, it does not introduce any new security issues.

...

JWT 로그인 예제를 구현해보기 위해 프로젝트를 하나 만들었다. 

https://codeburst.io/jwt-authorization-in-flask-c63c1acf4eeb 를 참조했다.


app.py

# -*- coding: utf-8 -*-
from flask import Flask, jsonify, request
from model.login import LoginAPI
from flask_restful import Api
from flask_jwt_extended import JWTManager
from flask_jwt_extended import (create_access_token, create_refresh_token, jwt_required, jwt_refresh_token_required, get_jwt_identity, get_raw_jwt)

# Flask app을 생성한다
app = Flask(__name__)
app.config['JWT_SECRET_KEY'] = 'jwt-secret'
api = Api(app)
jwt_manager = JWTManager()
jwt_manager.init_app(app)

@app.route('/')
def index():
    return "Hello world"

api.add_resource(LoginAPI, '/api/login')

@app.route('/protected')
@jwt_required
def protected():
    return "logged in!"

if __name__ == '__main__':
    app.run(host='127.0.0.1', debug=True)

간단하게 필요한것만 넣었다. app.config['JWT_SECRET_KEY']를 설정해 주지 않으면 에러가 난다. 홈 경로의 Hello world는 그냥 메인 페이지이고, 로그인이 되어야만 /protected경로로 들어가서 logged in!이라는 메시지를 볼 수 있다.


코드를 보면 알수있겠지만 jwt로그인이 된 후 들어갈 페이지의 제한을 걸려면 @jwt_required데코레이터를 사용해주면 된다. @jwt_required데코레이터가 있는 페이지는, 들어갈때 Authorization헤더에 'Bearer access_token'포맷으로 토큰 값을 보내줘야 한다. 아래에 프로그래밍 예시가 있으니 아래에서 살펴보자.


login.py

# -*- coding: utf-8 -*-

from flask_restful import Resource
from flask_jwt_extended import create_access_token, create_refresh_token

class LoginAPI(Resource):
    def post(self):
        token_identity = {'username': 'testuser'}
        access_token = create_access_token(identity=token_identity)
        refresh_token = create_refresh_token(identity=token_identity)
        
        login_info = ({'access_token': access_token,
                       'refresh_token': refresh_token,
                       'user_name': 'testuser'})
        
        return login_info, 200

실제 로그인 체크하는 디비나 ldap로직은 다 뺀 기본적인 token만 반환하는 페이지이다. post로 요청을 받으면 access_token, refresh_token, user_name을 반환한다.


로그인을 테스트해볼 test_request.py이다.

# -*- coding: utf-8 -*-
import urllib2
import json

url = 'http://127.0.0.1:5000/api/login'
req = urllib2.Request(url, 'sdfsfsdfsdf')
page = urllib2.urlopen(req).read()
print page
token = json.loads(page)

protected_url = 'http://127.0.0.1:5000/protected'
req = urllib2.Request(protected_url)
req.add_header('Authorization', 'Bearer {}'.format(token['access_token']))
print urllib2.urlopen(req).read()

app.py를 실행시킨 상태에서 이 테스트코드를 돌리면 /api/login에 아무 post데이터나 넣어서 로그인을 한다. 그 후에 access_token값을 저장해놓고, 인증이 필요한 /protected페이지에 들어갈때 Authorization헤더를 세팅해주면 logged in!이 잘 나온다. Authorization헤더를 세팅 하지말고 요청을 보낼시 401 인증 에러가 나는걸 확인할수 있다.