チーム内勉強会のために作成したJSON Web Tokenについての資料です。
JSON Web Tokenboot camp 2020ryo.ito (@ritou)
View Slide
͜ͷࢿྉ͕ΉGOALJSON Web TokenͱͲΜͳͷ͔Λཧղ͢Δ৭ʑͳαʔϏεɺγεςϜͰΘΕ͍ͯΔJSON WebSignatureͷΈʹ͍ͭͯཧղ͢ΔϢʔεέʔεͱઃܭ/࣮ͷϙΠϯτΛཧ͠ɺۀͰ҆શʹJWTΛѻ͑ΔΑ͏ʹͳΔ
JSON Web Token֓ཁ
3'$+40/8FC5PLFO +85“JSON Web Token (JWT) is a compact, URL-safe means of representing claims to betransferred between two parties.”
JSON Web Tokenͱ͍ΖΜͳσʔλ(ߏԽ͞ΕͨͷόΠφϦ·Ͱ)ΛෳͷαʔϏεɺγεςϜؒͰΓͱΓ͢ΔͨΊʹURLηʔϑͳจࣈྻʹΤϯίʔυ͢ΔΈ͘͠Τϯίʔυ͞Εͨจࣈྻࣗମ͕JWTͱݺΕ͍ͯΔॺ໊Λ͚ͭͨΓ(JSON Web Signature, JWS)ɺ҉߸ԽͰ͖Δ(JSON Web Encryption, JWE)
JWTੜͷ͖͔͚ͬOpenIDϑΝϯσʔγϣϯʹΑΔOpenID Connectͷ༷ࡦఆʹ߹ΘͤͯIETFͷJOSE WGʹ༷ͯࡦఆ։࢝ϢʔβʔใɺೝূΠϕϯτใͷड͚͠ʹར༻SAMLͰΘΕ͖ͯͨʮॊೈ͔ͭෳࡶͰ͋ΔXMLॺ໊ʯΑΓ༰қʹ࣮Ͱ͖ɺίϯύΫτʹදݱͰ͖ΔηΩϡϦςΟτʔΫϯΛࢦͨͦ͠ΕͰ·༷ͩͷҰ෦͔͠ΘΕ͍ͯͳ͍
Ϣʔεέʔεൃߦऀ/ड৴ऀʹ୯ҰͷαʔϏεɺγεςϜ͕ൃߦˍड৴ൃߦͱड৴Λߦ͏αʔϏεɺγεςϜ͕ผ
Ϣʔεέʔε:୯ҰͷαʔϏεɺγεςϜ͕ൃߦˍड৴WebΞϓϦέʔγϣϯͷηογϣϯCookieϩάΠϯதͷϢʔβʔใΛ֨ೲHTTP Responseͱͯ͠ൃߦɺWebϒϥβ͕อ࣋ɺHTTP Requestͱͯ͠ड৴
Ϣʔεέʔε:୯ҰͷαʔϏεɺγεςϜ͕ൃߦˍड৴WebΞϓϦέʔγϣϯͷCSRFରࡦτʔΫϯηογϣϯʹඥͮ͘(ηογϣϯIDͷϋογϡͳͲ)Λ֨ೲHTMLϑΥʔϜʹࢦఆɺPOSTσʔλͱͯ͠ड৴
Ϣʔεέʔε:ൃߦͱड৴Λߦ͏αʔϏεɺγεςϜ͕ผWeb APIΛར༻͢ΔࡍͷೝՄ༻τʔΫϯAPIΞΫηεʹඞཁͳϢʔβʔใͳͲΛ֨ೲೝূαʔόʔ͕ΫϥΠΞϯτʹൃߦɺAPIϦΫΤετʹ༩ͯ͠APIαʔόʔ͕ड৴
Ϣʔεέʔε:ൃߦͱड৴Λߦ͏αʔϏεɺγεςϜ͕ผWeb APIΛར༻͢Δࡍͷॺ໊͖ͭϦΫΤετ3rdύʔςΟʔΞϓϦ͕ൃߦɺೝূαʔόʔ͕ड৴ιʔγϟϧϩάΠϯʹ͓͚ΔϢʔβʔใͷୡೝূαʔόʔ͕ൃߦɺ3rdύʔςΟʔΞϓϦ͕ड৴
ϝϦοτ/σϝϦοτϝϦοτॊೈͳσʔλߏΛΓͱΓՄೳॺ໊ʹΑΔൃߦऀ/ड৴ऀͷݕূɺ༗ޮظݶ͚ͭΒΕΔσϝϦοτ҉߸ԽͰͳ͍ͷͰதΛ͚Δ֨ೲ͢ΔใʹΑͬͯσʔλαΠζ͕૿େ
ීٴ͍ͯ͠Δཧ༝༷͕RFCԽ͞Ε͓ͯΓɺϥΠϒϥϦॆ࣮ඪ४ԽϓϩτίϧͰͷ࠾༻࣮ಠࣗͷॺ໊͖ͭΤϯίʔσΟϯά͔ΒͷҠߦͳͲཱ֬͞ΕͨϕετϓϥΫςΟεRFC8725 JSON Web Token BCP
JWT vs Cookie?SPAͷจ຺ͰJWT = WebStorageʹτʔΫϯอଘ+APIϦΫΤετͱ͍͏ղऍ͕͞Ε͍ͯΔηογϣϯID + Cookieͱൺֱ͞ΕΔ͕JWT͋͘·ͰΤϯίʔυํ๏ͳͷͰ͕·ͱ·Βͳ͍แܕ vs ηογϣϯID͘͠จࣈྻ + HTTPCookieͷଐੑͱͷൺֱͳͲཧ͕ඞཁ
JWT = εςʔτϨε?JWT=εςʔτϨεͱ͍͏ݻఆ؍೦͍ͬͨͳ͍ใΛแ “Ͱ͖Δ” ಛੑΛ͍࣋ͬͯΔ͕ɺͦΕʹࢀরͷͨΊͷΩʔΛ࣋ͬͯྑ͍σʔλετΞͱͷΈ߹ΘͤΛߟྀ͢Δͱ෯͍Ϣʔεέʔεʹద༻Մೳ
༷ղઆ
RFCs (7515 ~ 7519)αʔϏεɺγεςϜؒͷΓͱΓʹඞཁͳϝλσʔλʁ ->RFC7519 JSON Web Tokenॺ໊ؔ࿈(ੜɺݕূɺඞཁͳύϥϝʔλ) -> RFC7515 JSONWeb Signature҉߸Խ -> RFC7516 JSON Web Encryption҉߸Խॺ໊ͷͨΊͷ伴දݱ -> RFC7517 JSON Web KeyΞϧΰϦζϜ -> RFC7518 JSON Web Algorithms
RFC7515 JSON Web Signature
ͱ͋ΔจࣈྻeyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
ࢲʹ͜͏ݟ͑·͢eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
͜ͷจࣈྻͷਖ਼ମRFC7515 JSON Web SignatureJWS Compact Serialization : ୯Ұͷॺ໊Λ࣋ͭγϦΞϥΠζܗࣜෳͷॺؚ໊͕ΊΒΕΔJWS JSON Serializationͱ͍͏ͷ͋Δ͕ΘΕ͍ͯΔͷݟ͔͚ͳ͍
HeadereyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXkEncoded header
HeaderBase64 URL Encode͞ΕͨJWS Header{\"typ\":\"JWT\",\r\n \”alg\”:\”HS256\"}JWSࣗମͷछྨॺ໊ʹؔ͢ΔύϥϝʔλΛؚΉ{“͔Β࢝·Δ෦͕eyJͱͳΔ
PayloadeyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXkEncoded payload
PayloadBase64 URL Encode͞ΕͨJWS Payload{\"iss\":\"joe\",\r\n \"exp\":1300819380,\r\n \"http://example.com/is_root\":true}PayloadJSONʹݶΒͳ͍͕ɺJSONʹؚΉඪ४తͳΫϨʔϜ(ύϥϝʔλ)ͷ͕ RFC7519 ʹͯఆٛ͞Ε͍ͯΔൃߦऀɺड৴/ར༻ऀɺ༗ޮظݶͳͲ
SignatureeyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXkEncoded signature
SignatureBase64 URL Encode͞ΕͨJWS SignatureEncoded Header ͱ Encoded PayloadΛ࿈݁ͨ͠ͷΛBase Stringͱͯ͠ར༻(໘ͳਖ਼نԽෆཁ)͜ͷΛੜ͢ΔࡍͷΞϧΰϦζϜ͕RFC7518, 伴දݱ͕RFC7517Ͱఆٛ͞Ε͍ͯΔ
RFC7519 JSON Web Token
JWTΫϨʔϜʮ୭͕ൃߦʁ୭͕ར༻ʁ୭ͷσʔλΛදݱʁʯ“jti” : JWTࣗମͷࣝผࢠ.ϦϓϨΠ߈ܸରࡦͳͲʹར༻.“iss” : ൃߦऀͷࣝผࢠ.υϝΠϯαʔϏεࣝผࢠ.“sub” : JWTͷओޠͱͳΔओମͷࣝผࢠ. ϢʔβʔͳͲ.“aud” : JWTͷड৴ऀɺར༻ऀͷࣝผࢠ
JWTΫϨʔϜʮ͍͔ͭΒ͍ͭ·Ͱ༗ޮʁ͍ͭൃߦ͞Εͨʁʯ“iat” : ൃߦ࣌“exp” : ༗ޮظݶ“nbf” : ༗ޮظݶͷ։࢝࣌
JWTΫϨʔϜͷྫ (OIDC)
JWTΫϨʔϜશͯར༻ඞਢͰͳ͍ : ίϯςΩετʹΑͬͯબϥΠϒϥϦʹΑͬͯݕূػೳΛ͍࣋ͬͯΔͷݕূͷཻͳͲɺཁ݅Λຬ͔ͨ͢ͷ֬ೝඞཁ
RFC7518 JSON Web Algorithms
ॺ໊༻ΞϧΰϦζϜ“none” : ॺ໊ͳ͠“HS256”, “HS384”, “HS512” : HMAC SHA-XXX“RS256”, “RS384”, “RS512” : RSASSA-PKCS1-v1_5“PS256”, “PS384”, “PS512” : RSASSA-PSS“ES256”, “ES384”, “ES512” : ECDSAϋογϡؔ + ڞ༗伴Ͱॺ໊Λੜൃߦɺݕূ͕ಉҰͷ߹ͳͲͰར༻
ॺ໊༻ΞϧΰϦζϜ“none” : ॺ໊ͳ͠“HS256”, “HS384”, “HS512” : HMAC SHA-XXX“RS256”, “RS384”, “RS512” : RSASSA-PKCS1-v1_5“PS256”, “PS384”, “PS512” : RSASSA-PSS“ES256”, “ES384”, “ES512” : ECDSARSAॺ໊ൿີ伴Ͱॺ໊ੜɺެ։伴ͰݕূRS256͕Α͘ΘΕ͍ͯΔ͕…
ॺ໊༻ΞϧΰϦζϜ“none” : ॺ໊ͳ͠“HS256”, “HS384”, “HS512” : HMAC SHA-XXX“RS256”, “RS384”, “RS512” : RSASSA-PKCS1-v1_5“PS256”, “PS384”, “PS512” : RSASSA-PSS“ES256”, “ES384”, “ES512” : ECDSAପԁۂઢॺ໊ൿີ伴Ͱॺ໊ੜɺެ։伴Ͱݕূ࠷ۙͷϓϩτίϧͰESܥ͕ਓؾ
ΞϧΰϦζϜͷ͍͚ൃߦ/ड৴͕ಉҰ : HSXXXڞ༗ൿີ伴Λ҆શʹཧ͢Δൃߦ/ड৴͕ผ : RSXXX, PSXXX, ESXXXൃߦଆ͕ड৴ଆʹެ։伴Λ͢߹ʹΑ͓ͬͯޓ͍ʹެ։伴Λ͠߹͏
RFC7517 JSON Web Key
伴ʹؔ͢Δ༷伴ͷදݱ伴ϖΞ(ެ։伴ɺൿີ伴)ɺରশ伴伴ͷηοτͷදݱϩʔςʔγϣϯαϙʔτ͢ΔΞϧΰϦζϜͷมߋ
伴දݱͷͨΊͷύϥϝʔλ“kty” : 伴ͷछྨ “RSA”, “EC”, “oct”“use” : “sig”“key_ops” : “sign”, “verify”“alg” : “RS256”, … , “PS256”, … , “ES256”, …“kid” : 伴ͷࣝผࢠ“x5u”, “x5c”, “x5t”, “x5t#s256” : X.509ূ໌ॻؔ࿈
伴ͷදݱ : ରশ伴
伴ͷදݱ : ൿີ伴(RSA)
伴ͷදݱ : ެ։伴(RSA)
伴ͷදݱ : ެ։伴(ପԁۂઢ)
伴ηοτͷදݱ (Google)
Ϣʔεέʔε༗ޮͳެ։伴ใΛެ։jwks_url : JSON ܗࣜͰ伴ใͷηοτΛฦ͢ઃఆϑΝΠϧͰͷอ࣋ൿີ伴
JWT(JWS)࣮ͷϙΠϯτ
RFC8725JSON Web Token BCPhttps://qiita.com/ritou/items/71e58fbc0c5605ec61cb
JSON Web SignatureΛ؆୯͔ͭ҆શʹ͏ͨΊͷkid/typύϥϝʔλͷ͍ํhttps://ritou.hatenablog.com/entry/2020/03/31/142550
JWT(JWS)Λ҆શʹ͏ͨΊͷϙΠϯτPayloadʹؚΉใΛΑ͘ݕ౼͢Δॺ໊ݕূॲཧΛ࣮֬ʹߦ͏ෳͷJWT(JWS)Λར༻͢Δࡍ༻్Λࢦఆ͠ɺഉଞతʹݕূ͢Δ
JWT(JWS)Λ҆શʹ͏ͨΊͷϙΠϯτPayloadʹؚΉใΛΑ͘ݕ౼͢Δ(Ϣʔεέʔεґଘ)ॺ໊ݕূॲཧΛ࣮֬ʹߦ͏(ϥΠϒϥϦΛར༻)ෳͷJWT(JWS)Λར༻͢Δࡍ༻్Λࢦఆ͠ɺഉଞతʹݕূ͢Δ
PayloadʹؚΉใΛΑ͘ݕ౼͢Δࣗॗ
ॺ໊ݕূॲཧΛ࣮֬ʹߦ͏ॺ໊ݕূ࣌ͷΞϧΰϦζϜͷΛͲ͔͜ΒҾ͔͘HeaderͷalgύϥϝʔλͷΛૉʹ͏ͱ߈ܸΛड͚ΔڪΕnoneʹมߋ͞ΕͯεΩοϓ͞ΕͨΓRS256 -> HS256 Ͱެ։伴ͷϋογϡΛࢦఆ͞ΕͨΓཧ͍ͯ͠Δ伴ʹඥͮ͘Λར༻͠ɺHeaderͷͦͷͱͷൺֱʹཹΊΔ
༻్ͱॺ໊ݕূʹ༻్ͷදݱͱࢦఆॺ໊ੜɺݕূ༻ͷ伴ͷཧ্هͷݕূ
༻్ͷදݱͱࢦఆ͑Δύϥϝʔλෳ͋ΔHeader“typ” ύϥϝʔλ (ྫ: “secevent+jwt”) ɿ伴ཧͱ“kid” ύϥϝʔλ : ༻్͝ͱʹ伴ࣗମΛ͚ΔPayloadಠࣗΫϨʔϜ : “usage” ͳͲ
༻్ͷදݱͱࢦఆͲΕΛ͏͔ॊೈʹஅ͖͢ػೳ୯ҐͰ伴Λ͚ΒΕΔ : Header “kid”伴पΓ͍͡Εͳ͍͕Header͍͡ΕΔ : Header “typ”伴पΓHeader͍͡Εͳ͍ : ಠࣗΫϨʔϜ
“kid” Λ༻͍ͨ༻్ͷཧ༻్͝ͱʹ伴ϦετΛΘ͚ɺॺ໊ݕূ࣌ʹར༻ॺ໊ݕূͱ༻్ͷݕূΛ݉ͶΔਓ͕ؒΘ͔Γ͍͢Α͏ʹ “(༻్) + (ϥϯμϜͳจࣈྻͱ͔ͱ͔)” ͱ͍͏idʹ͢Δ
“typ” Λ༻͍ͨ༻్ͷཧॺ໊ݕূલʹఆͰ͖ΔϥΠϒϥϦʹΑͬͯࢦఆͰ͖ͳ͍ɺࢦఆͰ͖ͯࣗಈͰݕূͰ͖ͳ͍ͷ͋ΔͷͰҙ
ಠࣗΫϨʔϜͷར༻ॺ໊ݕূޙͷఆͱͳΔࢦఆɺݕূͱʹಠࣗͷ࣮ͱͳΔ
JWSੜɺݕূσϞʢΔ࣌ؒͳͦ͞͏ʣ
తॺ໊͖ͭͷJSON Web Token(JSON Web Signature)ͷॺ໊ੜ/ݕূΛମݧ࣮ͰϥΠϒϥϦΛ͏͜ͱΛ͓קΊ͠·͢ɻ
ඞཁͳػೳ͜ΕΒͷػೳ͕ඞཁͰ͢ɻϓϩάϥϛϯάݴޠʹΑͬͯྻͷॲཧͳͲɺຊઆ໌ͱҟͳΔ݁ՌͱͳΔ߹͋Γ·͢ɻBase64 URL Encode / Decode (Paddingͳ͠)JSON Encode / DecodeHMAC-SHA256
JWTੜͷྲྀΕ1. HeaderΛੜ2. PayloadΛੜ3. SignatureΛੜ4. ࿈݁ͯ͠
(1) Headerར༻͢ΔHeaderύϥϝʔλ“typ” : “handson+JWT” # ϋϯζΦϯ༻ʹಠࣗఆٛ“alg” : “HS256” # HMAC-SHA256 ར༻Λએݴ“kid” : “handson01” # 伴ཧΛҙࣝ͢ΔͨΊʹར༻
(1) Header1. JSON Encode“{\"alg\":\"HS256\",\"kid\":\"handson01\",\"typ\":\"handson+JWT\"}”2. Base64 URL Encode“eyJhbGciOiJIUzI1NiIsImtpZCI6ImhhbmRzb24wMSIsInR5cCI6ImhhbmRzb24rSldUIn0”
(2) PayloadૹΓ͍ͨσʔλ“Foo”:”Bar”“Hoge”:”Fuga”
(2) Payload1. JSON Encode"{\"Foo\":\"Bar\",\"Hoge\":\"Fuga\"}"2. Base64 URL Encode“eyJGb28iOiJCYXIiLCJIb2dlIjoiRnVnYSJ9"
(3) Signature1. Header, PayloadΛ“.”Ͱ࿈݁ͤͯ͞Base StringΛ࡞“eyJhbGciOiJIUzI1NiIsImtpZCI6ImhhbmRzb24wMSIsInR5cCI6ImhhbmRzb24rSldUIn0.eyJGb28iOiJCYXIiLCJIb2dlIjoiRnVnYSJ9”
(3) Signature2. Base StringΛHMAC-SHA256ͨ͠ΛBase64 URLEncode伴 : “THIS_IS_SAMPLE_KEY_FOR_JWT_HANDSON”“Tp0zcg2nEA1r94EijoymQTTVMwH6iaLoOpxEZf3KcVM”
(4) Base StringͱSignatureͷΛ“.”Ͱ࿈݁͢Δͱ“eyJhbGciOiJIUzI1NiIsImtpZCI6ImhhbmRzb24wMSIsInR5cCI6ImhhbmRzb24rSldUIn0.eyJGb28iOiJCYXIiLCJIb2dlIjoiRnVnYSJ9.Tp0zcg2nEA1r94EijoymQTTVMwH6iaLoOpxEZf3KcVM”
JWTݕূͷྲྀΕ1. HeaderΛݕূ2. SignatureΛݕূ3. (PayloadΛݕূ)
(1) HeaderBase64 URL Decode & JSON Decodeͨ݁͠ՌΛݕূ“typ” : “handson+JWT” # ظ͢ΔͱҰக͢Δ?“kid” : “handson01” # αϙʔτ͍ͯ͠Δ伴?“alg” : “HS256” # kidʹඥͮ͘伴ͱΞϧΰϦζϜ͕Ұக͢Δ?
(2) Signatureੜͱಉ༷ʹHeader, PayloadΛ“.”Ͱ࿈݁ͤͯ͞BaseStringΛ࡞“eyJhbGciOiJIUzI1NiIsImtpZCI6ImhhbmRzb24wMSIsInR5cCI6ImhhbmRzb24rSldUIn0.eyJGb28iOiJCYXIiLCJIb2dlIjoiRnVnYSJ9”
(2) Signature2. Headerʹࢦఆ͞Εͨkidʹඥͮ͘伴ͰɺBase StringΛHMAC-SHA256ͨ͠ΛBase64 URL Encodeͯ͠ൺֱ伴 : “THIS_IS_SAMPLE_KEY_FOR_JWT_HANDSON”“Tp0zcg2nEA1r94EijoymQTTVMwH6iaLoOpxEZf3KcVM”※ެ։伴҉߸Λར༻͢Δ߹ॺ໊ݕূ༻ͷؔΛར༻
(3) Payloadॺ໊ݕূ͕ऴΘͬͨޙʹඞཁͳΒPayloadΛݕূ(ࠓճRFC7519Ͱఆٛ͞Ε͍ͯΔiss, aud, expͳͲͷΫϨʔϜΛؚΜͰ͍ͳ͍ͨΊݕূෆཁ)
https://jwt.io/ ͰݕূՄೳ