Upgrade to Pro — share decks privately, control downloads, hide ads and more …

JWT와 Flask, PyJWT로 인증 API 서버 만들기

Avatar for Junho Kim Junho Kim
August 25, 2018

JWT와 Flask, PyJWT로 인증 API 서버 만들기

PyCon 2018 KR Tutorial slide

Avatar for Junho Kim

Junho Kim

August 25, 2018
Tweet

Other Decks in Programming

Transcript

  1. $POUFOUT  ਋ܻо੉Ѧ৵ೞחо   ੋૐੋооߺѱ࢓ಝࠁӝ  ੋૐߑधрױೞѱ࢓ಝࠁӝ  +85ۆ

      1ZUIPOীࢲ+85ܳࢎਊೞחੋૐ"1*ࢲߡٜ݅ӝ  "EWBODFE5PQJDT Ҋਵә઱ઁٜ
  2. కୡীחইפҊ୐ਢࢲ࠺झח  ࢲ࠺झ೧ঠೞחݽٚӝמਸױੌࢲߡী޻য֍Ҋ .POPSJUIJD ࢲ࠺झܳઁ ҕೞחഋక৓णפ׮  ૓૞ 8FC 8"4

    %#  +41 5PNDBU 4FSWMFU .Z4RM   ױੌࢲߡীࣁ࣌੿ࠁী׮֍ਵݶҊ޹ೡѪহ঻ભ 6TFS 8FC 8"4 %# GET / index.html (or jsp) ਃ୒ (೙ਃೞݶ) DB query Querying Data DB ޚ਷ index ಕ੉૑ (৘ࢂ) index ಕ੉૑
  3. ੉3FRVFTUоפѪ੉ջ੄੉ग  4UBUFMFTTೠ3&45ীࢲTUBUFܳਬ૑೧ঠೞח࢚ടߊࢤ  ޛ֤؀ࠗ࠙੄3&45"1*חೞա੄SFRVFTU۽୊ܻܳ՘ղঠೞחѪ੉જणפ ׮ ղࠗDPOUSPMMFSীࢲٮ۽۽૒ਸجܻחೠ੉੓؊ۄب   Ӓؘ۠QSPUFDUFESFTPVSDFח

     ࠁഐ೧ঠೞח੗ਗFH ౠ੿ࢎਊ੗оࢎਊೠ SFTPVSDF੿ࠁоઉয়ӝ١  ߐڰ ੉۞۰ݶݒߣੋૐࢲߡীMPHJOೞҊਃ୒୊ܻೞҊ೧ঠೣ 6TFS 8FC "1*4FSWFS "1*4FSWFS GET / (۽Ӓੋ റ) resource A ઱ࣁਃ ਽ resource A resource B ઱ࣁਃ ־ҳջ ք Context ҕਬ X
  4. ੋૐҗੋо੄ҳࢿ  ࢎਊ੗  ࢎਊ੗੄ੋૐ੿ࠁ *%18   ࢲ࠺झઁҕࢲߡ 

    पઁ਋ܻоઁҕೞҊर਷ࢲ࠺झ  ੋૐ߂ࢎਊ੉ೲо ੋо ػࢎਊ੗݅ࢎਊоמ  ੋૐੋоࢲߡ  ࢎਊ੗੄ੋૐਸ׸׼  ੋૐػࢎਊ੗੄ࢲ࠺झࢎਊӂೠҙܻ
  5. ੋૐ  ੋૐ "VUIFOUJDBUJPO   ؀࢚ࢲ࠺झ PSࢲߡ ীղо־ҳੋ૑ܳഛੋ߉חੌ 

    ࠁా ੋૐਸೞӝਤೠ੿ࠁܳCPEZ۽୎ࠗೞৈਃ୒  *%18 UPLFO ੋૐػTFTTJPO١ ੋૐࢲߡ 6TFS tղо#PC੉ਃu ੋૐ t਽ইפঠu ੋૐѾҗࢲ࠺झী١۾ػࢎਊ੗഑਷١۾غ૑ঋ਷ࢎਊ੗ ੋૐ%# tੴ#PC੐ u t֨u
  6. ੋо  ੋо "VUIPSJ[BUJPO   ղо੉੘স SFTPVSDF ਸࣻ೯ೡӂೠ "VUIPSJUZ

    о੓׮Ҋഛੋ߉חੌ  ੋоܳਃ୒ೞחࢎਊ੗חੋૐ੉غয੓যঠೣ  ࠁా਷ౠ੿೯ਤ BDUJPO ী؀ೞৈӂೠ੉੓׮ USVF হ׮ GBMTF ܳ߈ജ ੋоࢲߡ ࠁాੋૐࢲߡ৬э੉੓਺ ࢲ࠺झઁҕࢲߡ 6TFS tա#PCੋؘ  7.ೞա݅઱ۄu ࢲ࠺झਃ୒ t#PC੉7.ٜ݅ӂೠ੓যਃ u ੋоӂೠ୓௼ ঠॳ USVF ੗ৈӝ7. ࢲ࠺झѾҗ
  7. 4FTTJPOӝ߈ੋૐੋоߑध  ੹ా੸ੋߑध  $MJFOU੄DPPLJFীੋૐੋо੿ࠁܳ֍૑ঋ਺ FH DPPLJF<rJT@MPHJOs>   TFTTJPO੄LFZчਸਬ૑ೞৈ

    ࢲߡীࢲਃ୒ೠTFTTJPOEJDUী۽Ӓੋਬޖ 5SVF'BMTF ܳঌইࠁחߑध  ݒ਋рױ  ׮઺&OUFSQSJTFࢲߡীࢲחࣁ࣌ਸҕਬೞחӝࣿ೙ਃ  4FTTJPODMVTUFSJOH
 PWFSIFBE  $PPLJFܳੜTBMU૕೧ࢲঐഐച
 WVMOFSBCJMJUZ from flask import Flask, request, session app = Flask(__name__) @app.route('/is_login,', methods=['POST']) def is_login(): return session['logged_in'] # True / False
  8. ੌ߈੸ੋ 5PLFOӝ߈ੋૐੋоߑध  4FTTJPOীҳগ߉૑ঋҊ SFRVFTUীੋૐੋоҙ۲੿ࠁܳೣԋࠁղ୊ܻ  ੌ߈੸ਵ۽IUUQSFRVFTU੄IFBEFSীUPLFO੿ࠁܳನೣೞৈ੹࣠  ۽ӒੋBDDFTT@UPLFOߊә 

    0"VUICFBSFS 0"VUI0QFO*% +85١ ੋૐࢲߡ 6TFS *%#PC 18#PC#PC#PC ష௾ߊәਃ୒ ష௾ߊә BDDFTT@UPLFOOBXLEKOBLKTDOLj ࢲ࠺झઁҕࢲߡ ࢲ࠺झਃ୒ IFBEFSBDDFTT@UPLFOOBXLj 63*(&5TFSWJDF
  9. ৵0"VUIউॄਃ  ਋ࢶѐ֛੟ӝ  0"VUIח3'$ী੿੄ػੋૐੋоQSPUPDPMTUBOEBSE 3'$   য়ט਋ܻоࢎਊೞҊ੗ೞח+85חੋૐীࢎਊೞחUPLFO੄GPSNBU 

    ৈ۞࠙੉ࢤпೞח0"VUI ,ࢎ੄۽Ӓੋ0"VUI (ࢎ੄۽Ӓੋ0"VUI 'ࢎ੄ 0"VUI חݽف0"VUIQSPUPDPMਸ݅઒दఃח*NQMFNFOUBUJPO  ਋ܻоঌҊ੓ח0"VUI0QFO*%ੑפ׮  0QFO*%ߑध਷৻ࠗܳ׮֗য়ӝٸޙীղࠗݎઁೠ١੄೐ۄ੉ࡁࢲ࠺झী חࠗ੸೤
  10. +40/8FC5PLFO  ӝઓ੄੄޷হחBDDFTTUPLFOী੄޷ܳࠗৈ೧ࠁ੗  5PLFOഛੋױ҅ ੋо ܳੋૐࢲߡীޚ૑ঋҊ"1*ࢲߡࣻળীࢲ೧Ѿೞ੗  ޚ૑بٮ૑૑ب݈Ҋযڌѱࢤѹݡ঻חо 

     ѐ੄QBSU IFBEFS QBZMPBE WFSJGZTJHOBUVSF    EPU ਵ۽ҳ࠙ - header.payload.signature eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. (ࢎਊೠ algorithm, token type: JWT) eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ. (ੋૐ ੿ࠁ SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c (Ѩૐਸ ਤೠ Signature)
  11. +85JOEFUBJM - Header  ೧׼UPLFO੉ࢎਊೞחBMHPSJUIN  5PLFO5ZQF - Payload 

    UPLFO੄ӂೠ੿ࠁ  ੋૐ੿ࠁ ࢎਊ੗੿ࠁ TDPQF ݅ܐӝр  - Signature  ਤ߸ઑ੼ѨਸਤೠTJHOBUVSF
  12. 1BZMPBEJOEFUBJM  Payloadীನೣغחղਊٜ JO3'$ SFTFSWFEGJFME  iss (issuer)UPLFOߊ೯੗  sub

    (subject)ࢎਊ੗৬ࢎਊ੗SFTPVSDF63*ઁݾ  aud (audience)ߊә؀࢚  exp (expiration date)ష௾੉݅ܐغחदр  nbf (not before)ష௾੄ഝࢿചदр ੉ч੉റࠗఠష௾੉ഝࢿച   iat (issued at)ష௾ߊәदр  jti (JWT ID)5PLFOҳ߹ਸਤೠVOJRVF*%
 
 scopeUPLFOࢎਊ੗੄HSBOUػӂೠ QSJWBUFGJFME ݽٚ Payload Fieldܳ ࢎਊೡ ೙ਃח হ׮!
  13. +85ܳࢎਊೠੋૐ੺ର Client Server (ੋૐ + API) 1045 MPHJOFOEQPJOU XJUI*% 18

    "1*4FSWFS SFEJSFDU  ੋૐࢲߡ  ੋૐറ+85ష௾ߊә +85ష௾ਸ#SPXTFSীੜ੷੢ ௢ఃPSTFTTJPO 4FSWJDFSFRVFTUXJUI+85UPLFOJO"VUIPSJ[BUJPO)FBEFS +85੄TJHOBUVSFѨૐ ೙ਃೠ҃਋ +85ীࢲ "VUIPSJUZѨૐറࢲ࠺झઁҕ 1SPUFDUFE3FTPVSDF4FSWJDFSFTQPOTF
  14. 8IZ+85  ੹׳߉਷UPLFOী؀ೞৈੋૐࢲߡী೧׼UPLFO੄WBMJEJUZৈࠗܳޛযࠅ೙ਃ оহ਺  ޖ۰JOEVTUSZTUBOEBSE 3'$   5PLFOӝ߈ੋૐߑधীࢲUPLFO੄GPSNBUਵ۽ੋ੿߉਺

    ޙઁب੓૑݅ٯ ൤؀উ੉   ইפ੉ѢUPLFOఎஂظࢲڙэ਷UPLFOਵ۽SFRVFTUզܻݶޙઁغחѪইצ оਃ   э਷ష௾ী؀ೞৈэ਷ࢎۈੋध਷э਷*%18۽э਷ࢎۈੋधҗэ਷ޙ ઁ +85੄ࠁউࢿҗח׮ܲোѾా۽੄ޙઁ
  15. BVUI@TFSWFSQZ from flask import Flask, jsonify import jwt import datetime

    app = Flask(__name__) secret_key = ‘secret_key' # This should be handled by configuration DB or third-party tool @app.route('/') def index(): return "Hello auth server” # Test for server is working @app.route("/get_token") def get_token(): # db ੘স੉ ೙ਃ೤פ׮. (ౚషܻ঴ীࢲח ࢤۚ೤פ׮.) issuer = 'pycon_tutorial' subject = ‘localhost:5000/v1' date_time_obj = datetime.datetime exp_time = date_time_obj.timestamp(date_time_obj.utcnow() + datetime.timedelta(hours=24)) scope = ['v1', 'v2'] payload = { 'sub': subject, 'iss': issuer, 'exp': int(exp_time), 'scope': scope } token = jwt.encode(payload, secret_key, algorithm='HS256') return jsonify({ 'msg': 'token is generated', 'access_token': str(token) }), 201
  16. BVUI@TFSWFSQZ ҅ࣘ @app.route("/admin_login") def admin_login(): issuer = ‘pycon_tutorial' # scope੉

    ׮ܲ login਷ যڌѱ ୊ܻೡө? subject = 'localhost:5000/v1' date_time_obj = datetime.datetime exp_time = date_time_obj.timestamp(date_time_obj.utcnow() + datetime.timedelta(hours=24)) scope = ['v1', 'v2', 'v3'] payload = { 'sub': subject, 'iss': issuer, 'exp': int(exp_time), 'scope': scope } token = jwt.encode(payload, secret_key, algorithm='HS256') return jsonify({ 'msg': 'token is generated', 'access_token': str(token) }), 201 app.run(port=5001)
  17. TFSWJDF@TFSWFSQZ from flask import Flask, jsonify, abort, request import jwt

    import requests from functools import wraps app = Flask(__name__) secret_key = 'secret_key' # third-party ో۽ ҙܻغযঠ ೤פ׮. def jwt_token_required(f): # flask-jwt-extendedীب ઁҕೞח Ѫ୊ۢ ؘ௏ۨ੉ఠ ୶оೞӝ / customizing оמೞب۾ self ҳഅ @wraps(f) def decorated_function(*args, **kwargs): # header ର੓ח૑ # token੉ ੿࢚੸ੋѤ૑ if not 'Authorization' in request.headers: return jsonify({ 'msg': 'token is not given' }), 400 token = request.headers['Authorization'] try: decoded_token = jwt.decode(token, secret_key, algorithms=['HS256']) except: return jsonify({ 'msg': 'Invalid token given' }), 400 kwargs['decoded_token'] = decoded_token return f(*args, **kwargs) return decorated_function @app.route('/') def index(): return "Hello Pycon Server"
  18. TFSWJDF@TFSWFSQZ ҅ࣘ @app.route('/login') def login(): r = requests.get(“http://127.0.0.1:5001/get_token") # login਷

    auth-serverী ష௾ਸ ߊә߉ח ೯ਤ rsp_json = r.json() token = rsp_json['access_token'] return jsonify({ 'access_token': token, 'msg': "Successfully login" }), 200 @app.route("/protected_service") @jwt_token_required def protected_service(**kwargs): return jsonify({ 'msg': 'hello protected user' }), 200 @app.route("/v3") @jwt_token_required def other_scope_service(**kwargs): token = kwargs['decoded_token'] if 'v3' in token[‘scope']: # ୶о۽ ӂೠ ഛੋ ೞח ੘স / decoratorܳ ࣻ੿ೞৈ ੉ ࠗ࠙ਸ ઁѢೡ ࣻب ੓णפ׮. # service logic return jsonify({ 'msg': 'welcome v3 user' }), 200 else: return jsonify({ 'msg': 'You are not authorized' }), 401 app.run()
  19. 5PLFOਸউ੹ೞѱࢎਊೞחߑߨ  5PLFO਷߈٘द)UUQ0OMZDPPLJFী੷੢೤פ׮  944ҕѺߑ૑  ௢ఃܳࢎਊೞৈ+85ܳ੷੢ೠ׮ݶ $43'ী؀ೠߑযܳ೧ঠ೤פ׮  ਋ܻоݽܰחࢎ੉ী׮ܲبݫੋীࢲਃ୒੉ৢࣻ੓਺

     4FDSFULFZחੋૐ"1*ࢲߡ ࢲ࠺झ"1*ࢲߡ݅о૑Ҋ੓যঠ೤פ׮  ݒߣష௾ਸࠁউః۽Ѩૐ೧ࢲৢ߄ܲఃੋ૑Ѩૐ೧ঠೣ  ъ۱ೠఃߑध DMJFOUীח֢୹غ૑ঋইঠೣ  ޹хೠؘ੉ఠחQBZMPBEী֍૑ঋইঠ೤פ׮  3FQMBZҕѺਸߑ૑ೞӝਤ೧ࢲחUPLFO੄ਬബदрਸૣѱ೤פ׮  3VMFPGUIVNCחઓ੤ೞ૑ঋ਺ઁҕೞחࢲ࠺झীٮۄࢲ
  20. 3FGSFTI5PLFO  ߊәೠUPLFOਸୌ֙݅֙ࢎਊೡࣻחহणפ׮  ੌ੿ӝр੉૑դUPLFO਷੤ߊәਸ೧ঠ೤פ׮  ࢲ࠺झܳਃ୒ೞ׮оࢎਊೞ؍UPLFO੄ӝр੉݅ܐػ҃਋ SFGSFTI@UPLFOਸ ٜҊష௾੤ߊәਃ୒ਸ೧ঠ೤פ׮ 

    5PLFOࠁҙߑߨ਷ࢎਊ੗੄ഋకীٮۄࢲ  access_token਷TFSWJDF"1*ࢲߡࣻળীࢲ TFDSFULFZEFDPEFܳా೧ ࢲ ୊ܻоמೞ૑݅ SFGSFTI@UPLFO਷߈٘दBVUIࢲߡܳా೧ࢲೠߣ؊Ѩૐ ੉೙ਃ೤פ׮  ࢎਊоמೠUPLFO੄੤ߊә੉޲۽ ECBDDFTT೙ਃ
  21. $MBJN֢୹JTTVF  +85חױࣽ൤IFBEFS DMBJN TJHOBUVSFܳCBTFFODPEJOHೠTUSJOH  TJHOBUVSFחTFDSFULFZоನೣػTBMUFETUSJOH੉ۄఎஂغযبղਊ੉߸ઑ غݶޙઁ9  DMBJN਷ࢎਊ੗੄ੋૐ੿ࠁоӒ؀۽֢୹ؼ਋۰о੓਺

     VTFS੄FNBJM١ӝఋࢎਊ੗੄ѐੋ੿ࠁо֢୹ؼ਋۰  оמೠ+85ীࠛ೙ਃೠ੿ࠁܳ੸ѱ׸ইঠ೤פ׮  FNBJMэ਷ ߈੿ب QVCMJDೠ੿ࠁח֢୹੉غחࣽр਋ܻTFSWJDFחইפ؊ ۄب׮ܲ੄޷۽ޙઁоࢤӡࣻب੓਺  ژח׮ܲMBZFS੄ب਑ )5514 DMBJNਸ୶оঐഐചب޹хೠࢲ࠺झীࢲח Ҋ۰оמ