Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
JWT와 Flask, PyJWT로 인증 API 서버 만들기
Search
Junho Kim
August 25, 2018
Programming
0
2.4k
JWT와 Flask, PyJWT로 인증 API 서버 만들기
PyCon 2018 KR Tutorial slide
Junho Kim
August 25, 2018
Tweet
Share
Other Decks in Programming
See All in Programming
20260127_試行錯誤の結晶を1冊に。著者が解説 先輩データサイエンティストからの指南書 / author's_commentary_ds_instructions_guide
nash_efp
1
990
AtCoder Conference 2025
shindannin
0
1.1k
Apache Iceberg V3 and migration to V3
tomtanaka
0
170
15年続くIoTサービスのSREエンジニアが挑む分散トレーシング導入
melonps
2
230
Rust 製のコードエディタ “Zed” を使ってみた
nearme_tech
PRO
0
210
HTTPプロトコル正しく理解していますか? 〜かわいい猫と共に学ぼう。ฅ^•ω•^ฅ ニャ〜
hekuchan
2
690
余白を設計しフロントエンド開発を 加速させる
tsukuha
7
2.1k
登壇資料を作る時に意識していること #登壇資料_findy
konifar
4
1.6k
副作用をどこに置くか問題:オブジェクト指向で整理する設計判断ツリー
koxya
1
610
MUSUBIXとは
nahisaho
0
140
Lambda のコードストレージ容量に気をつけましょう
tattwan718
0
140
AI Agent の開発と運用を支える Durable Execution #AgentsInProd
izumin5210
7
2.3k
Featured
See All Featured
The Success of Rails: Ensuring Growth for the Next 100 Years
eileencodes
47
7.9k
My Coaching Mixtape
mlcsv
0
49
SEOcharity - Dark patterns in SEO and UX: How to avoid them and build a more ethical web
sarafernandez
0
120
How Fast Is Fast Enough? [PerfNow 2025]
tammyeverts
3
460
Effective software design: The role of men in debugging patriarchy in IT @ Voxxed Days AMS
baasie
0
230
The Language of Interfaces
destraynor
162
26k
Money Talks: Using Revenue to Get Sh*t Done
nikkihalliwell
0
150
Pawsitive SEO: Lessons from My Dog (and Many Mistakes) on Thriving as a Consultant in the Age of AI
davidcarrasco
0
67
Agile that works and the tools we love
rasmusluckow
331
21k
A better future with KSS
kneath
240
18k
Building Better People: How to give real-time feedback that sticks.
wjessup
370
20k
Why Our Code Smells
bkeepers
PRO
340
58k
Transcript
+85৬'MBTL 1Z+85۽ੋૐ"1*ࢲߡٜ݅ӝ 1:$0/,PSFBౚషܻ ӣળഐ
$POUFOUT ܻоѦ৵ೞחо ੋૐੋооߺѱಝࠁӝ ੋૐߑधрױೞѱಝࠁӝ +85ۆ
1ZUIPOীࢲ+85ܳࢎਊೞחੋૐ"1*ࢲߡٜ݅ӝ "EWBODFE5PQJDT Ҋਵәઁٜ
য়טౚషܻীࢲܖחѪܖঋחѪ য়טౚషܻীࢲחղਊਸಝࠅѪੑפ рױೠੋૐੋоJOUSPEVDUJPO 'MBTL৬1Z+85ܳࢎਊೠੋૐ"1*ࢲߡ য়טౚషܻীࢲחղਊನೣೞҊঋणפ
ੋૐੋоܳ৵೧ঠೞաਃ 3#"$ 1ZUIPOEFDPSBUPSܳҳഅೞחߑߨ 'MBTLXFCBQQEFTJHOQBUUFSO
ܻоѦ৵ೞחо .PUJWBUJPO
కୡীחইפҊਢࢲ࠺झח ࢲ࠺झ೧ঠೞחݽٚӝמਸױੌࢲߡীয֍Ҋ .POPSJUIJD ࢲ࠺झܳઁ ҕೞחഋకणפ 8FC 8"4
%# +41 5PNDBU 4FSWMFU .Z4RM ױੌࢲߡীࣁ࣌ࠁী֍ਵݶҊೡѪহભ 6TFS 8FC 8"4 %# GET / index.html (or jsp) ਃ (ਃೞݶ) DB query Querying Data DB ޚ index ಕ (ࢂ) index ಕ
दрաݶࢲ 'SPOUFOEӝࣿبߊೞҊৈ۞оࢲ࠺झҳࢿইఃఫبਊغणפ tۿоঌইࢲ۪؊݂ೞҊߔূ٘ীਃೡԋߔূ٘חؘఠ݅۴u tߔূ٘חۿоਗೞחࢲ࠺झܳೞח"1*݅ઁҕೞݶغѷu ױఋ۽֗য়פ ࢚కਬܳೞঋח3&45ࢲ࠺झૐо
पदр۪؊݂দ 6TFS 8FC "1*4FSWFS %# GET / Index ಕ ௪݂௪݂௪݂.. Querying Data Index.html (৮ࢿغח ࢂ) index ಕ Resource list Resource list
ӏݽоழݶࢲ ഒѢೞӝחޖܻפӬդ"ࢲ࠺झ݅ઁҕೡѱցח#ࢲ࠺झ݅ઁҕ ೞ ࢲ࠺झױਤ۽ࢲߡࣁ࠙ച झாੌইਓ "1*4FSWFS "1*4FSWFS "1*4FSWFS
%# पदр۪؊݂দ 6TFS 8FC GET / (৮ࢿغח ࢂ) index ಕ ࣻ ݆ request
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
ੋૐੋооߺѱಝࠁӝ
ੋૐҗੋоҳࢿ ࢎਊ ࢎਊੋૐࠁ *%18 ࢲ࠺झઁҕࢲߡ
पઁܻоઁҕೞҊरࢲ࠺झ ੋૐ߂ࢎਊೲо ੋо ػࢎਊ݅ࢎਊоמ ੋૐੋоࢲߡ ࢎਊੋૐਸ ੋૐػࢎਊࢲ࠺झࢎਊӂೠҙܻ
ੋૐ ੋૐ "VUIFOUJDBUJPO ࢚ࢲ࠺झ PSࢲߡ ীղо־ҳੋܳഛੋ߉חੌ
ࠁా ੋૐਸೞӝਤೠࠁܳCPEZ۽ࠗೞৈਃ *%18 UPLFO ੋૐػTFTTJPO١ ੋૐࢲߡ 6TFS tղо#PCਃu ੋૐ tইפঠu ੋૐѾҗࢲ࠺झী١۾ػࢎਊ١۾غঋࢎਊ ੋૐ%# tੴ#PC u t֨u
ੋо ੋо "VUIPSJ[BUJPO ղоস SFTPVSDF ਸࣻ೯ೡӂೠ "VUIPSJUZ
оҊഛੋ߉חੌ ੋоܳਃೞחࢎਊחੋૐغযযঠೣ ࠁాౠ೯ਤ BDUJPO ীೞৈӂೠ USVF হ GBMTF ܳ߈ജ ੋоࢲߡ ࠁాੋૐࢲߡ৬э ࢲ࠺झઁҕࢲߡ 6TFS tա#PCੋؘ 7.ೞա݅ۄu ࢲ࠺झਃ t#PC7.ٜ݅ӂೠযਃ u ੋоӂೠ ঠॳ USVF ৈӝ7. ࢲ࠺झѾҗ
ੋૐߑधрױೞѱಝࠁӝ
4FTTJPOӝ߈ੋૐੋоߑध ాੋߑध $MJFOUDPPLJFীੋૐੋоࠁܳ֍ঋ FH DPPLJF<rJT@MPHJOs> TFTTJPOLFZчਸਬೞৈ
ࢲߡীࢲਃೠ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
ੌ߈ੋ 5PLFOӝ߈ੋૐੋоߑध 4FTTJPOীҳগ߉ঋҊ SFRVFTUীੋૐੋоҙ۲ࠁܳೣԋࠁղܻ ੌ߈ਵ۽IUUQSFRVFTUIFBEFSীUPLFOࠁܳನೣೞৈ࣠ ۽ӒੋBDDFTT@UPLFOߊә
0"VUICFBSFS 0"VUI0QFO*% +85١ ੋૐࢲߡ 6TFS *%#PC 18#PC#PC#PC షߊәਃ షߊә BDDFTT@UPLFOOBXLEKOBLKTDOLj ࢲ࠺झઁҕࢲߡ ࢲ࠺झਃ IFBEFSBDDFTT@UPLFOOBXLj 63*(&5TFSWJDF
৵0"VUIউॄਃ ࢶѐ֛ӝ 0"VUIח3'$ীػੋૐੋоQSPUPDPMTUBOEBSE 3'$ য়טܻоࢎਊೞҊೞח+85חੋૐীࢎਊೞחUPLFOGPSNBU
ৈ۞࠙ࢤпೞח0"VUI ,ࢎ۽Ӓੋ0"VUI (ࢎ۽Ӓੋ0"VUI 'ࢎ 0"VUI חݽف0"VUIQSPUPDPMਸ݅दఃח*NQMFNFOUBUJPO ܻоঌҊח0"VUI0QFO*%ੑפ 0QFO*%ߑध৻ࠗܳ֗য়ӝٸޙীղࠗݎઁೠ١ۄࡁࢲ࠺झী חࠗ
ѱо SFRVFTUоUPLFOਸٜҊ৳Ҋ೧ࢲޖࢲ࠺झܳઁҕೡࣻহणפ UPLFOSBOEPN@HFOFSBUFE@TUSJOH UPLFO೧ӏਵ۽ߊәೠѪੋഛੋ %#.4 PUIFS"1* ਃ
UPLFOࢲ࠺झप೯ӂೠоҊחبഛੋਃ
+85ۆ
+40/8FC5PLFO ӝઓহחBDDFTTUPLFOীܳࠗৈ೧ࠁ 5PLFOഛੋױ҅ ੋо ܳੋૐࢲߡীޚঋҊ"1*ࢲߡࣻળীࢲ೧Ѿೞ ޚبٮب݈Ҋযڌѱࢤѹݡחо
ѐQBSU IFBEFS QBZMPBE WFSJGZTJHOBUVSF EPU ਵ۽ҳ࠙ - header.payload.signature eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. (ࢎਊೠ algorithm, token type: JWT) eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ. (ੋૐ ࠁ SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c (Ѩૐਸ ਤೠ Signature)
+85JOEFUBJM - Header ೧UPLFOࢎਊೞחBMHPSJUIN 5PLFO5ZQF - Payload
UPLFOӂೠࠁ ੋૐࠁ ࢎਊࠁ TDPQF ݅ܐӝр - Signature ਤ߸ઑѨਸਤೠTJHOBUVSF
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ܳ ࢎਊೡ ਃח হ!
+85ܳࢎਊೠੋૐର Client Server (ੋૐ + API) 1045 MPHJOFOEQPJOU XJUI*% 18
"1*4FSWFS SFEJSFDU ੋૐࢲߡ ੋૐറ+85షߊә +85షਸ#SPXTFSীੜ ఃPSTFTTJPO 4FSWJDFSFRVFTUXJUI+85UPLFOJO"VUIPSJ[BUJPO)FBEFS +85TJHOBUVSFѨૐ ਃೠ҃ +85ীࢲ "VUIPSJUZѨૐറࢲ࠺झઁҕ 1SPUFDUFE3FTPVSDF4FSWJDFSFTQPOTF
8IZ+85 ׳߉UPLFOীೞৈੋૐࢲߡী೧UPLFOWBMJEJUZৈࠗܳޛযࠅਃ оহ ޖ۰JOEVTUSZTUBOEBSE 3'$ 5PLFOӝ߈ੋૐߑधীࢲUPLFOGPSNBUਵ۽ੋ߉
ޙઁب݅ٯ উ ইפѢUPLFOఎஂظࢲڙэUPLFOਵ۽SFRVFTUզܻݶޙઁغחѪইצ оਃ эషীೞৈэࢎۈੋधэ*%18۽эࢎۈੋधҗэޙ ઁ +85ࠁউࢿҗחܲোѾా۽ޙઁ
1ZUIPOীࢲ+85ܳࢎਊೞחੋૐ"1*ࢲߡٜ݅ӝ
3FRVJSFEQBDLBHFT য়טࢎਊೡౚషܻীࢲࢎਊೡಁఃݾ۾ 1Z+85 'MBTL 3FRVFTUT
QJQJOTUBMM1Z+85'MBTLSFRVFTUT
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
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)
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"
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()
"EWBODFE5PQJDT
5PLFOਸউೞѱࢎਊೞחߑߨ 5PLFO߈٘द)UUQ0OMZDPPLJFীפ 944ҕѺߑ ఃܳࢎਊೞৈ+85ܳೠݶ $43'ীೠߑযܳ೧ঠפ ܻоݽܰחࢎীܲبݫੋীࢲਃৢࣻ
4FDSFULFZחੋૐ"1*ࢲߡ ࢲ࠺झ"1*ࢲߡ݅оҊযঠפ ݒߣషਸࠁউః۽Ѩૐ೧ࢲৢ߄ܲఃੋѨૐ೧ঠೣ ъ۱ೠఃߑध DMJFOUীח֢غঋইঠೣ хೠؘఠחQBZMPBEী֍ঋইঠפ 3FQMBZҕѺਸߑೞӝਤ೧ࢲחUPLFOਬബदрਸૣѱפ 3VMFPGUIVNCחઓೞঋઁҕೞחࢲ࠺झীٮۄࢲ
3FGSFTI5PLFO ߊәೠUPLFOਸୌ֙݅֙ࢎਊೡࣻחহणפ ੌӝрդUPLFOߊәਸ೧ঠפ ࢲ࠺झܳਃೞоࢎਊೞ؍UPLFOӝр݅ܐػ҃ SFGSFTI@UPLFOਸ ٜҊషߊәਃਸ೧ঠפ
5PLFOࠁҙߑߨࢎਊഋకীٮۄࢲ access_tokenTFSWJDF"1*ࢲߡࣻળীࢲ TFDSFULFZEFDPEFܳా೧ ࢲ ܻоמೞ݅ SFGSFTI@UPLFO߈٘दBVUIࢲߡܳా೧ࢲೠߣ؊Ѩૐ ਃפ ࢎਊоמೠUPLFOߊә۽ ECBDDFTTਃ
TFDSFU@LFZܳ߸҃ೞҊरযਃ ࢎप ઁח+85৬ҙ۲ݢઁ ࢜۽TFDSFU@LFZߊә+85৬ҙ۲হחօਸా೧ࢲߊәೞחѪݏ णפ о۸UIJSEQBSUZܳՙҊٜযоঠೠ؍о
FH )5514 PS44- DPOGJH@TFSWFSীࢲQVCTVCҳઑ۽ਊоמ
$MBJN֢JTTVF +85חױࣽIFBEFS DMBJN TJHOBUVSFܳCBTFFODPEJOHೠTUSJOH TJHOBUVSFחTFDSFULFZоನೣػTBMUFETUSJOHۄఎஂغযبղਊ߸ઑ غݶޙઁ9 DMBJNࢎਊੋૐࠁоӒ۽֢ؼ۰о
VTFSFNBJM١ӝఋࢎਊѐੋࠁо֢ؼ۰ оמೠ+85ীࠛਃೠࠁܳѱইঠפ FNBJMэ ߈ب QVCMJDೠࠁח֢غחࣽрܻTFSWJDFחইפ؊ ۄبܲ۽ޙઁоࢤӡࣻب ژחܲMBZFSب )5514 DMBJNਸ୶оঐഐചبхೠࢲ࠺झীࢲח Ҋ۰оמ
хࢎפ
"QQFOEJY"0"VUI'MPX From http://www.sauru.so/blog/basic-of-oauth2-and-jwt/
"QQFOEJY#0"VUI'MPX From http://earlybird.kr/1584