JJWT มุ่งหวังที่จะเป็นไลบรารี่ที่ใช้งานและเข้าใจง่ายที่สุดสำหรับการสร้างและตรวจสอบ JSON Web Tokens (JWT) และ JSON Web Keys (JWK) บน JVM และ Android
JJWT เป็นการนำ Java มาใช้โดยยึดตามข้อกำหนด JOSE Working Group RFC โดยเฉพาะ:
RFC 7519: JSON เว็บโทเค็น (JWT)
RFC 7515: ลายเซ็นเว็บ JSON (JWS)
RFC 7516: การเข้ารหัสเว็บ JSON (JWE)
RFC 7517: เว็บคีย์ JSON (JWK)
RFC 7518: อัลกอริทึมเว็บ JSON (JWA)
RFC 7638: รหัสประจำตัวคีย์เว็บ JSON
RFC 9278: URI รหัสประจำตัวของคีย์เว็บ JSON
RFC 7797: ตัวเลือกเพย์โหลดที่ไม่ได้เข้ารหัสของ JWS
RFC 8037: อัลกอริธึม Edwards Curve และ JWK
สร้างขึ้นโดย Les Hazlewood และได้รับการสนับสนุนและดูแลโดยชุมชนผู้มีส่วนร่วม
JJWT เป็นโอเพ่นซอร์สภายใต้เงื่อนไขของใบอนุญาต Apache 2.0
PublicKey
ส่วนตัวของ JWKtoString()
ความปลอดภัยทำงานได้อย่างสมบูรณ์บน Java 7+ JDK และ Android ทั้งหมด
แนวทางปฏิบัติที่ดีที่สุดและการยืนยันด้านความปลอดภัยอัตโนมัติ
เรียนรู้และอ่าน API ได้ง่าย
อินเทอร์เฟซที่สะดวกและอ่านง่าย เหมาะสำหรับการเติมข้อมูลอัตโนมัติของ IDE เพื่อเขียนโค้ดอย่างรวดเร็ว
เป็นไปตามข้อกำหนด RFC อย่างสมบูรณ์สำหรับฟังก์ชันการใช้งานทั้งหมด ทดสอบกับเวกเตอร์ทดสอบที่ระบุ RFC
การใช้งานที่เสถียรด้วยการทดสอบเกือบ 1,700 ครั้งและบังคับใช้การครอบคลุมโค้ดการทดสอบ 100% ทุกวิธี คำสั่ง และตัวแปรย่อยแบบมีเงื่อนไขในโค้ดเบสทั้งหมดได้รับการทดสอบและจำเป็นต้องส่งต่อทุกบิลด์
การสร้าง แยกวิเคราะห์ และตรวจสอบ JWT ขนาดกะทัดรัดที่เซ็นชื่อแบบดิจิทัล (หรือที่เรียกว่า JWS) ด้วยอัลกอริธึม JWS มาตรฐานทั้งหมด:
ตัวระบุ | อัลกอริธึมลายเซ็น |
---|---|
| HMAC ที่ใช้ SHA-256 |
| HMAC ที่ใช้ SHA-384 |
| HMAC ที่ใช้ SHA-512 |
| ECDSA โดยใช้ P-256 และ SHA-256 |
| ECDSA โดยใช้ P-384 และ SHA-384 |
| ECDSA ที่ใช้ P-521 และ SHA-512 |
| RSASSA-PKCS-v1_5 โดยใช้ SHA-256 |
| RSASSA-PKCS-v1_5 โดยใช้ SHA-384 |
| RSASSA-PKCS-v1_5 โดยใช้ SHA-512 |
| RSASSA-PSS ที่ใช้ SHA-256 และ MGF1 กับ SHA-256 1 |
| RSASSA-PSS ที่ใช้ SHA-384 และ MGF1 กับ SHA-384 1 |
| RSASSA-PSS ที่ใช้ SHA-512 และ MGF1 กับ SHA-512 1 |
| อัลกอริทึมลายเซ็นดิจิทัลของ Edwards-curve 2 |
1. ต้องใช้ Java 11 หรือผู้ให้บริการ JCA ที่เข้ากันได้ (เช่น BouncyCastle) ในคลาสพาธรันไทม์
2 . ต้องใช้ Java 15 หรือผู้ให้บริการ JCA ที่เข้ากันได้ (เช่น BouncyCastle) ในคลาสพาธรันไทม์
การสร้าง แยกวิเคราะห์ และถอดรหัส JWT ขนาดกะทัดรัดที่เข้ารหัส (หรือที่เรียกว่า JWE) ด้วยอัลกอริธึมการเข้ารหัส JWE มาตรฐานทั้งหมด:
ตัวระบุ | อัลกอริธึมการเข้ารหัส |
---|---|
| AES_128_CBC_HMAC_SHA_256 อัลกอริธึมการเข้ารหัสที่ได้รับการรับรองความถูกต้อง |
| อัลกอริธึมการเข้ารหัสที่ได้รับการรับรองความถูกต้อง AES_192_CBC_HMAC_SHA_384 |
| AES_256_CBC_HMAC_SHA_512 อัลกอริธึมการเข้ารหัสที่ได้รับการรับรองความถูกต้อง |
| AES GCM โดยใช้คีย์ 128 บิต 1 |
| AES GCM โดยใช้คีย์ 192 บิต 1 |
| AES GCM โดยใช้คีย์ 256 บิต 1 |
1 . ต้องใช้ Java 8 หรือผู้ให้บริการ JCA ที่เข้ากันได้ (เช่น BouncyCastle) ในคลาสพาธรันไทม์
อัลกอริธึมการจัดการคีย์ทั้งหมดสำหรับการรับคีย์การเข้ารหัสและถอดรหัส JWE:
ตัวระบุ | อัลกอริธึมการจัดการคีย์ |
---|---|
| RSAES-PKCS1-v1_5 |
| RSAES OAEP ใช้พารามิเตอร์เริ่มต้น |
| RSAES OAEP ใช้ SHA-256 และ MGF1 กับ SHA-256 |
| AES Key Wrap พร้อมค่าเริ่มต้นเริ่มต้นโดยใช้คีย์ 128 บิต |
| AES Key Wrap พร้อมค่าเริ่มต้นเริ่มต้นโดยใช้คีย์ 192 บิต |
| AES Key Wrap พร้อมค่าเริ่มต้นเริ่มต้นโดยใช้คีย์ 256 บิต |
| การใช้คีย์สมมาตรที่ใช้ร่วมกันโดยตรงเป็น CEK |
| ข้อตกลงคีย์คงที่ Elliptic Curve Diffie-Hellman Ephemeral โดยใช้ Concat KDF |
| ECDH-ES ใช้ Concat KDF และ CEK ห่อด้วย "A128KW" |
| ECDH-ES ใช้ Concat KDF และ CEK ห่อด้วย "A192KW" |
| ECDH-ES ใช้ Concat KDF และ CEK ห่อด้วย "A256KW" |
| การห่อคีย์ด้วย AES GCM โดยใช้คีย์ 128 บิต 1 |
| การห่อคีย์ด้วย AES GCM โดยใช้คีย์ 192 บิต 1 |
| การห่อคีย์ด้วย AES GCM โดยใช้คีย์ 256 บิต 1 |
| PBES2 พร้อมการห่อ HMAC SHA-256 และ "A128KW" 1 |
| PBES2 พร้อมการห่อ HMAC SHA-384 และ "A192KW" 1 |
| PBES2 พร้อมการห่อ HMAC SHA-512 และ "A256KW" 1 |
1 . ต้องใช้ Java 8 หรือผู้ให้บริการ JCA ที่เข้ากันได้ (เช่น BouncyCastle) ในคลาสพาธรันไทม์
การสร้าง แยกวิเคราะห์ และตรวจสอบ JSON Web Keys (JWK) ในรูปแบบคีย์ JWA มาตรฐานทั้งหมดโดยใช้ประเภท Java Key
ดั้งเดิม:
รูปแบบคีย์ JWK | ประเภท Key จาวา | JJWT ประเภท Jwk |
---|---|---|
คีย์สมมาตร | | |
คีย์สาธารณะรูปไข่ Curve | | |
คีย์ส่วนตัวรูปไข่ Curve | | |
กุญแจสาธารณะ RSA | | |
รหัสส่วนตัว RSA | | |
รหัสส่วนตัว XDH | | |
รหัสส่วนตัว XDH | | |
คีย์สาธารณะ EdDSA | | |
รหัสส่วนตัว EdDSA | | |
1 . ต้องใช้ Java 15 หรือผู้ให้บริการ JCA ที่เข้ากันได้ (เช่น BouncyCastle) ในคลาสพาธรันไทม์
2 . ต้องใช้ Java 15 หรือผู้ให้บริการ JCA ที่เข้ากันได้ (เช่น BouncyCastle) ในคลาสพาธรันไทม์
การปรับปรุงความสะดวกสบายที่เหนือกว่าข้อกำหนดเช่น
การบีบอัดเพย์โหลดสำหรับ JWT ขนาดใหญ่ ไม่ใช่แค่ JWE
การยืนยันการเรียกร้องค่าสินไหมทดแทน (ต้องระบุค่าเฉพาะ)
อ้างสิทธิ์ POJO marshaling และ unmarshalling เมื่อใช้ตัวแยกวิเคราะห์ JSON ที่เข้ากันได้ (เช่น Jackson)
การสร้างคีย์ความปลอดภัยตามอัลกอริธึม JWA ที่ต้องการ
และอื่นๆ…
การทำให้เป็นอนุกรมและการแยกวิเคราะห์แบบไม่กะทัดรัด
คุณลักษณะนี้อาจถูกนำมาใช้ในรุ่นต่อๆ ไป ยินดีต้อนรับการมีส่วนร่วมของชุมชน!
หากคุณมีปัญหาในการใช้ JJWT โปรดอ่านเอกสารในหน้านี้ก่อนที่จะถามคำถาม เราพยายามอย่างหนักเพื่อให้แน่ใจว่าเอกสารของ JJWT นั้นมีประสิทธิภาพ มีการจัดหมวดหมู่ตามสารบัญ และเป็นข้อมูลล่าสุดสำหรับแต่ละรุ่น
หากเอกสารประกอบหรือ API JavaDoc ไม่เพียงพอ และคุณมีคำถามเกี่ยวกับการใช้งานหรือสับสนเกี่ยวกับบางสิ่งบางอย่าง โปรดถามคำถามของคุณที่นี่ อย่างไรก็ตาม:
โปรดอย่าสร้างปัญหา GitHub เพื่อถามคำถาม
เราใช้ปัญหา GitHub เพื่อติดตามงานที่ดำเนินการได้ซึ่งจำเป็นต้องเปลี่ยนแปลงการออกแบบและ/หรือโค้ดเบสของ JJWT หากคุณมีคำถามเกี่ยวกับการใช้งาน โปรดถามคำถามของคุณที่นี่แทน แล้วเราจะแปลงเป็นปัญหาได้หากจำเป็น
หากมีการสร้างปัญหา GitHub ที่ไม่ได้แสดงถึงงานที่สามารถดำเนินการได้สำหรับโค้ดเบสของ JJWT ปัญหานั้นจะถูกปิดทันที
หากคุณไม่มีคำถามเกี่ยวกับการใช้งานและเชื่อว่าคุณมีจุดบกพร่องหรือคำขอคุณสมบัติที่ถูกต้อง โปรดพูดคุยที่นี่ ก่อน โปรดค้นหาอย่างรวดเร็วก่อนเพื่อดูว่ามีการสนทนาที่มีอยู่ที่เกี่ยวข้องกับคุณมีอยู่แล้วหรือไม่ และเข้าร่วมการสนทนาที่มีอยู่นั้นหากจำเป็น
หากคุณรู้สึกว่าต้องการช่วยแก้ไขจุดบกพร่องหรือใช้งานคุณลักษณะใหม่ด้วยตนเอง โปรดอ่านส่วนการสนับสนุนถัดไปก่อนที่จะเริ่มงานใดๆ
Simple Pull Requests ที่แก้ไขสิ่งอื่นใดนอกเหนือจากโค้ดหลัก JJWT (เอกสาร, JavaDoc, การพิมพ์ผิด, กรณีทดสอบ ฯลฯ) มักจะได้รับการชื่นชมเสมอและมีความเป็นไปได้สูงที่จะถูกรวมเข้าด้วยกันอย่างรวดเร็ว กรุณาส่งพวกเขา!
อย่างไรก็ตาม หากคุณต้องการหรือรู้สึกว่าจำเป็นต้องเปลี่ยนฟังก์ชันการทำงานหรือโค้ดหลักของ JJWT โปรดอย่าออกคำขอดึงโดยไม่เริ่มการสนทนา JJWT ใหม่ และหารือเกี่ยวกับการเปลี่ยนแปลงที่คุณต้องการ ก่อน ก่อนที่คุณจะเริ่มทำงาน
เป็นเรื่องน่าเสียดายที่จะปฏิเสธคำขอดึงที่จริงใจและซาบซึ้งอย่างแท้จริงของคุณ หากอาจไม่สอดคล้องกับเป้าหมายของโครงการ ความคาดหวังในการออกแบบ หรือฟังก์ชันการทำงานที่วางแผนไว้ น่าเสียดายที่เราต้องปฏิเสธ PR ขนาดใหญ่ในอดีต เนื่องจากไม่สอดคล้องกับความคาดหวังของโครงการหรือการออกแบบ ทั้งหมดนี้เป็นเพราะผู้เขียน PR ไม่ได้เช็คอินกับทีมก่อนก่อนที่จะดำเนินการแก้ไขปัญหา
ดังนั้น โปรดสร้างการสนทนา JJWT ใหม่ก่อนเพื่อหารือ จากนั้นเราจะเห็นการแปลงการสนทนาเป็นประเด็นได้อย่างง่ายดาย จากนั้นดูว่า (หรืออย่างไร) รับรองการประชาสัมพันธ์หรือไม่ ขอบคุณ!
หากคุณต้องการความช่วยเหลือ แต่ไม่รู้ว่าจะเริ่มต้นจากตรงไหน โปรดไปที่หน้าปัญหาที่ต้องการความช่วยเหลือ และเลือกปัญหาใดๆ ที่นั่น เรายินดีที่จะพูดคุยและตอบคำถามในความคิดเห็นของปัญหา
หากสิ่งใดไม่ดึงดูดคุณก็ไม่ต้องกังวล! ความช่วยเหลือใด ๆ ที่คุณต้องการนำเสนอจะได้รับการชื่นชมตามคำเตือนข้างต้นที่เกี่ยวข้องกับการร้องขอการดึงข้อมูล อย่าลังเลที่จะพูดคุยหรือถามคำถามก่อนหากคุณไม่แน่ใจ -
JSON Web Token (JWT) เป็นรูปแบบข้อความข้อความ ที่ใช้งานทั่วไป สำหรับการส่งข้อมูลด้วยวิธีที่กะทัดรัดและปลอดภัย ตรงกันข้ามกับความเชื่อที่นิยม JWT ไม่เพียงแต่มีประโยชน์สำหรับการส่งและรับโทเค็นประจำตัวบนเว็บเท่านั้น แม้ว่าจะเป็นกรณีการใช้งานที่พบบ่อยที่สุดก็ตาม JWT สามารถใช้เป็นข้อความสำหรับข้อมูลประเภท ใดก็ได้
JWT ในรูปแบบที่ง่ายที่สุดประกอบด้วยสองส่วน:
ข้อมูลหลักภายใน JWT เรียกว่า payload
และ
Object
JSON ที่มีคู่ชื่อ/ค่าที่แสดงถึงข้อมูลเมตาเกี่ยวกับ payload
และตัวข้อความเอง ที่เรียกว่า header
payload
JWT สามารถเป็นอะไรก็ได้ - อะไรก็ได้ที่สามารถแสดงเป็นอาร์เรย์ไบต์ เช่น สตริง รูปภาพ เอกสาร ฯลฯ
แต่เนื่องจาก header
JWT เป็น JSON Object
จึงสมเหตุสมผลที่ payload
JWT อาจเป็น JSON Object
ได้เช่นกัน ในหลายกรณี นักพัฒนาต้องการให้ payload
เป็น JSON ที่แสดงข้อมูลเกี่ยวกับผู้ใช้หรือคอมพิวเตอร์หรือแนวคิดเกี่ยวกับข้อมูลประจำตัวที่คล้ายกัน เมื่อใช้วิธีนี้ payload
จะเรียกว่าออบเจ็กต์ Claims
JSON และคู่ชื่อ/ค่าแต่ละคู่ภายในออบเจ็กต์นั้นจะเรียกว่า claim
- ข้อมูลแต่ละส่วนภายใน 'การอ้างสิทธิ์' บางอย่างเกี่ยวกับข้อมูลประจำตัว
และถึงแม้ว่าการ 'อ้างสิทธิ์' บางอย่างเกี่ยวกับตัวตนจะมีประโยชน์ แต่จริงๆ แล้วใครๆ ก็สามารถทำเช่นนั้นได้ สิ่งสำคัญคือคุณ ต้องเชื่อถือ การกล่าวอ้างโดยการตรวจสอบว่ามาจากบุคคลหรือคอมพิวเตอร์ที่คุณเชื่อถือ
คุณลักษณะที่ดีของ JWT คือสามารถรักษาความปลอดภัยได้หลายวิธี JWT สามารถลงนามแบบเข้ารหัส (ทำให้เป็นสิ่งที่เราเรียกว่า JWS) หรือเข้ารหัส (ทำให้เป็น JWE) นี่เป็นการเพิ่มชั้นการตรวจสอบที่มีประสิทธิภาพให้กับ JWT - ผู้รับ JWS หรือ JWE สามารถมีความมั่นใจในระดับสูงว่าสิ่งนี้มาจากบุคคลที่พวกเขาไว้วางใจโดยการตรวจสอบลายเซ็นหรือถอดรหัส คุณลักษณะของการตรวจสอบยืนยันนี้เองที่ทำให้ JWT เป็นตัวเลือกที่ดีสำหรับการส่งและรับข้อมูลที่ปลอดภัย เช่น การอ้างสิทธิ์ในตัวตน
สุดท้ายนี้ JSON ที่มีช่องว่างเพื่อให้มนุษย์สามารถอ่านได้นั้นดี แต่ก็ไม่ได้สร้างรูปแบบข้อความที่มีประสิทธิภาพมากนัก ดังนั้น JWT จึงสามารถ บีบอัด (และแม้แต่บีบอัด) ให้เหลือน้อยที่สุด - โดยทั่วไปคือสตริงที่เข้ารหัส Base64URL - เพื่อให้สามารถส่งผ่านเว็บได้อย่างมีประสิทธิภาพมากขึ้น เช่น ในส่วนหัว HTTP หรือ URL
เมื่อคุณมี payload
และ header
แล้ว พวกมันจะถูกบีบอัดสำหรับการส่งข้อมูลทางเว็บอย่างไร และ JWT สุดท้ายมีหน้าตาเป็นอย่างไร มาดูกระบวนการเวอร์ชันที่เรียบง่ายด้วยรหัสเทียม:
สมมติว่าเรามี JWT พร้อม header
JSON และเพย์โหลดข้อความธรรมดา:
ส่วนหัว
- "alg": "ไม่มี" -
น้ำหนักบรรทุก
สัญลักษณ์ที่แท้จริงของความฉลาดไม่ใช่ความรู้ แต่เป็นจินตนาการ
ลบช่องว่างที่ไม่จำเป็นทั้งหมดใน JSON:
String header = ' {"alg":"none"} '
String payload = ' The true sign of intelligence is not knowledge but imagination. '
รับไบต์ UTF-8 และเข้ารหัส Base64URL แต่ละรายการ:
String encodedHeader = base64URLEncode( header . getBytes( " UTF-8 " ) )
String encodedPayload = base64URLEncode( payload . getBytes( " UTF-8 " ) )
เข้าร่วมส่วนหัวที่เข้ารหัสและการอ้างสิทธิ์ด้วยอักขระจุด ('.'):
String compact = encodedHeader + ' . ' + encodedPayload + ' . '
JWT String compact
ที่ต่อกันสุดท้ายมีลักษณะดังนี้:
eyJhbGciOiJub25lIn0.VGhlIHRydWUgc2lnbiBvZiBpbnRlbGxpZ2VuY2UgaXMgbm90IGtub3dsZWRnZSBidXQgaW1hZ2luYXRpb24u.
สิ่งนี้เรียกว่า JWT ที่ 'ไม่มีการป้องกัน' เนื่องจากไม่มีการรักษาความปลอดภัย - ไม่มีลายเซ็นดิจิทัลหรือการเข้ารหัสเพื่อ 'ปกป้อง' JWT เพื่อให้แน่ใจว่าบุคคลที่สามไม่สามารถเปลี่ยนแปลงได้
หากเราต้องการเซ็นชื่อแบบดิจิทัลในแบบฟอร์มขนาดกะทัดรัด เพื่อที่อย่างน้อยเราจะรับประกันได้ว่าไม่มีใครเปลี่ยนแปลงข้อมูลโดยที่เราตรวจไม่พบ เราจะต้องดำเนินการเพิ่มเติมอีกสองสามขั้นตอนดังที่แสดงไว้ถัดไป
แทนที่จะเป็นเพย์โหลดข้อความธรรมดา ตัวอย่างถัดไปจะใช้เพย์โหลดประเภทที่พบบ่อยที่สุด - JSON อ้างสิทธิ์ออบ Object
ที่มีข้อมูลเกี่ยวกับข้อมูลประจำตัวเฉพาะ นอกจากนี้เรายังจะลงนาม JWT แบบดิจิทัลเพื่อให้แน่ใจว่าบุคคลที่สามไม่สามารถเปลี่ยนแปลงได้โดยที่เราไม่รู้ตัว
สมมติว่าเรามี header
JSON และ payload
การอ้างสิทธิ์ :
ส่วนหัว
{
"alg" : " HS256 "
}
น้ำหนักบรรทุก
{
"sub" : " Joe "
}
ในกรณีนี้ header
ระบุว่าจะใช้อัลกอริทึม HS256
(HMAC ที่ใช้ SHA-256) เพื่อลงนาม JWT แบบเข้ารหัส นอกจากนี้ ออบเจ็กต์ payload
JSON ยังมีการอ้างสิทธิ์รายการเดียว โดยมีค่า sub
Joe
มีการเรียกร้องมาตรฐานจำนวนหนึ่งเรียกว่าการเรียกร้องที่ลงทะเบียนในข้อกำหนดและ sub
(สำหรับ 'หัวเรื่อง') ก็เป็นหนึ่งในนั้น
ลบช่องว่างที่ไม่จำเป็นทั้งหมดในออบเจ็กต์ JSON ทั้งสอง:
String header = ' {"alg":"HS256"} '
String claims = ' {"sub":"Joe"} '
รับ UTF-8 ไบต์และเข้ารหัส Base64URL แต่ละรายการ:
String encodedHeader = base64URLEncode( header . getBytes( " UTF-8 " ) )
String encodedClaims = base64URLEncode( claims . getBytes( " UTF-8 " ) )
เชื่อมต่อส่วนหัวที่เข้ารหัสและการอ้างสิทธิ์ด้วยอักขระจุด '." ตัวคั่น:
String concatenated = encodedHeader + ' . ' + encodedClaims
ใช้ความลับในการเข้ารหัสหรือคีย์ส่วนตัวที่แข็งแกร่งเพียงพอ พร้อมด้วยอัลกอริธึมการลงนามที่คุณเลือก (เราจะใช้ HMAC-SHA-256 ที่นี่) และลงนามในสตริงที่ต่อกัน:
SecretKey key = getMySecretKey()
byte [] signature = hmacSha256( concatenated, key )
เนื่องจากลายเซ็นเป็นอาร์เรย์ไบต์เสมอ Base64URL จึงเข้ารหัสลายเซ็นและรวมเข้ากับสตริง concatenated
ด้วยอักขระจุด '." ตัวคั่น:
String compact = concatenated + ' . ' + base64URLEncode( signature )
และคุณก็ได้แล้ว String compact
สุดท้ายจะมีลักษณะดังนี้:
EYJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJKb2UifQ.1KP0SsvENi7Uz1oQc07aXTL7kpQG5jBNIybqr60AlD4
สิ่งนี้เรียกว่า 'JWS' - ย่อมาจาก JWT ที่ลงนามแล้ว
แน่นอนว่าไม่มีใครอยากทำเช่นนี้ด้วยตนเองในโค้ด และที่แย่กว่านั้นคือ หากคุณทำอะไรผิดพลาด คุณก็อาจทำให้เกิดปัญหาด้านความปลอดภัยและจุดอ่อนร้ายแรงได้ ด้วยเหตุนี้ JJWT จึงถูกสร้างขึ้นเพื่อจัดการทั้งหมดนี้ให้กับคุณ: JJWT ดำเนินการทั้งการสร้าง JWS และการแยกวิเคราะห์และการตรวจสอบ JWS ให้คุณโดยอัตโนมัติโดยสมบูรณ์
จนถึงตอนนี้เราได้เห็น JWT ที่ไม่มีการป้องกันและ JWT ที่ลงนามด้วยการเข้ารหัส (เรียกว่า 'JWS') สิ่งหนึ่งที่มีอยู่ในทั้งสองสิ่งนี้ก็คือทุกคนสามารถเห็นข้อมูลทั้งหมดในนั้นได้ - ข้อมูลทั้งหมดในทั้งส่วนหัวและเพย์โหลดจะเปิดเผยต่อสาธารณะ JWS เพียงแต่ทำให้แน่ใจว่าข้อมูลจะไม่ถูกเปลี่ยนแปลงโดยใครก็ตาม - มันไม่ได้ป้องกันไม่ให้ใครเห็นข้อมูลนั้น หลายครั้งที่วิธีนี้เป็นเรื่องปกติ เนื่องจากข้อมูลภายในนั้นไม่ใช่ข้อมูลที่ละเอียดอ่อน
แต่ถ้าคุณต้องการแสดงข้อมูลใน JWT ซึ่งถือ เป็น ข้อมูลที่ละเอียดอ่อน อาจเป็นที่อยู่ทางไปรษณีย์ หมายเลขประกันสังคม หรือหมายเลขบัญชีธนาคารของใครบางคน
ในกรณีเหล่านี้ เราต้องการ JWT ที่เข้ารหัสโดยสมบูรณ์ หรือเรียกสั้นๆ ว่า 'JWE' JWE ใช้การเข้ารหัสเพื่อให้แน่ใจว่าเพย์โหลดยังคงได้รับการเข้ารหัส และ รับรองความถูกต้องอย่างสมบูรณ์ ดังนั้นบุคคลที่ไม่ได้รับอนุญาตจึงไม่สามารถดูข้อมูลภายใน หรือเปลี่ยนแปลงข้อมูลโดยไม่ถูกตรวจพบ โดยเฉพาะอย่างยิ่ง ข้อกำหนด JWE กำหนดให้ใช้อัลกอริทึมการเข้ารหัสรับรองความถูกต้องด้วยข้อมูลที่เกี่ยวข้องเพื่อเข้ารหัสและปกป้องข้อมูลอย่างสมบูรณ์
ภาพรวมทั้งหมดของอัลกอริทึม AEAD อยู่นอกขอบเขตสำหรับเอกสารนี้ แต่นี่คือตัวอย่างของ JWE ขนาดกะทัดรัดขั้นสุดท้ายที่ใช้อัลกอริทึมเหล่านี้ (ตัวแบ่งบรรทัดมีไว้เพื่อให้อ่านได้เท่านั้น):
EYJhbGciOiJBMTI4S1ciLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0. 6KB707dM9YTIgHtLvtgWQ8mKwboJW3of9locizkDTHzBC2IlrT1oOQ. AxY8DCtDaGlsbGljb3RoZQ. KDlTtXchhZTGufMYmOYGS4HffxPSUrfmqCHXaI9wOGY U0m_YmjN04DJvceFICbCVQ
ต่อไป เราจะกล่าวถึงวิธีการติดตั้ง JJWT ในโปรเจ็กต์ของคุณ จากนั้นเราจะดูวิธีใช้ API ที่ดีของ JJWT แทนการจัดการสตริงที่มีความเสี่ยงเพื่อสร้าง JWT, JWS และ JWE อย่างรวดเร็วและปลอดภัย
ใช้เครื่องมือสร้างที่เข้ากันได้กับ Maven ที่คุณชื่นชอบเพื่อดึงการขึ้นต่อกันจาก Maven Central
การขึ้นต่อกันอาจแตกต่างกันเล็กน้อยหากคุณกำลังทำงานกับโปรเจ็กต์ JDK หรือโปรเจ็กต์ Android
หากคุณกำลังสร้างโปรเจ็กต์ JDK (ที่ไม่ใช่ Android) คุณจะต้องกำหนดการขึ้นต่อกันต่อไปนี้:
< dependency >
< groupId >io.jsonwebtoken</ groupId >
< artifactId >jjwt-api</ artifactId >
< version >0.12.6</ version >
</ dependency >
< dependency >
< groupId >io.jsonwebtoken</ groupId >
< artifactId >jjwt-impl</ artifactId >
< version >0.12.6</ version >
< scope >runtime</ scope >
</ dependency >
< dependency >
< groupId >io.jsonwebtoken</ groupId >
< artifactId >jjwt-jackson</ artifactId > <!-- or jjwt-gson if Gson is preferred -->
< version >0.12.6</ version >
< scope >runtime</ scope >
</ dependency >
<!-- Uncomment this next dependency if you are using:
- JDK 10 or earlier, and you want to use RSASSA-PSS (PS256, PS384, PS512) signature algorithms.
- JDK 10 or earlier, and you want to use EdECDH (X25519 or X448) Elliptic Curve Diffie-Hellman encryption.
- JDK 14 or earlier, and you want to use EdDSA (Ed25519 or Ed448) Elliptic Curve signature algorithms.
It is unnecessary for these algorithms on JDK 15 or later.
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId> or bcprov-jdk15to18 on JDK 7
<version>1.76</version>
<scope>runtime</scope>
</dependency>
-->
dependencies {
implementation ' io.jsonwebtoken:jjwt-api:0.12.6 '
runtimeOnly ' io.jsonwebtoken:jjwt-impl:0.12.6 '
runtimeOnly ' io.jsonwebtoken:jjwt-jackson:0.12.6 ' // or 'io.jsonwebtoken:jjwt-gson:0.12.6' for gson
/*
Uncomment this next dependency if you are using:
- JDK 10 or earlier, and you want to use RSASSA-PSS (PS256, PS384, PS512) signature algorithms.
- JDK 10 or earlier, and you want to use EdECDH (X25519 or X448) Elliptic Curve Diffie-Hellman encryption.
- JDK 14 or earlier, and you want to use EdDSA (Ed25519 or Ed448) Elliptic Curve signature algorithms.
It is unnecessary for these algorithms on JDK 15 or later.
*/
// runtimeOnly 'org.bouncycastle:bcprov-jdk18on:1.76' // or bcprov-jdk15to18 on JDK 7
}
โปรเจ็กต์ Android จะต้องกำหนดการพึ่งพาต่อไปนี้และข้อยกเว้นของ Proguard และ Provider
BouncyCastle ที่เป็นตัวเลือก:
เพิ่มการพึ่งพาให้กับโครงการของคุณ:
dependencies {
api( ' io.jsonwebtoken:jjwt-api:0.12.6 ' )
runtimeOnly( ' io.jsonwebtoken:jjwt-impl:0.12.6 ' )
runtimeOnly( ' io.jsonwebtoken:jjwt-orgjson:0.12.6 ' ) {
exclude( group : ' org.json ' , module : ' json ' ) // provided by Android natively
}
/*
Uncomment this next dependency if you want to use:
- RSASSA-PSS (PS256, PS384, PS512) signature algorithms.
- EdECDH (X25519 or X448) Elliptic Curve Diffie-Hellman encryption.
- EdDSA (Ed25519 or Ed448) Elliptic Curve signature algorithms.
** AND ALSO ensure you enable the BouncyCastle provider as shown below **
*/
// implementation('org.bouncycastle:bcprov-jdk18on:1.76') // or bcprov-jdk15to18 for JDK 7
}
คุณสามารถใช้กฎการยกเว้น Android Proguard ต่อไปนี้:
-รักษาคุณสมบัติ InnerClasses - เก็บคลาส io.jsonwebtoken.** { *; - -เก็บชื่อคลาส io.jsonwebtoken.* { *; - -รักษาอินเทอร์เฟซ io.jsonwebtoken.* { *; - -เก็บคลาส org.bouncycastle.** { *; - -keepnames คลาส org.bouncycastle.** { *; - -dontwarn org.bouncycastle.**
หากคุณต้องการใช้อัลกอริธึม JWT RSASSA-PSS (เช่น PS256
, PS384
และ PS512
), EdECDH ( X25512
หรือ X448
) การเข้ารหัส Elliptic Curve Diffie-Hellman, อัลกอริธึมลายเซ็น EdDSA ( Ed25519
หรือ Ed448
) หรือคุณเพียงต้องการให้แน่ใจว่า Android ของคุณ แอปพลิเคชันกำลังใช้งาน BouncyCastle เวอร์ชันอัปเดต คุณจะต้อง:
ยกเลิกหมายเหตุการพึ่งพา BouncyCastle ตามที่แสดงความคิดเห็นไว้ด้านบนในส่วนการอ้างอิง
แทนที่ผู้ให้บริการ BC
แบบกำหนดเองของ Android รุ่นเก่าด้วยผู้ให้บริการที่อัปเดต
การลงทะเบียนผู้ให้บริการจะต้องดำเนินการ ตั้งแต่เนิ่นๆ ในวงจรการใช้งานของแอปพลิเคชัน โดยเฉพาะอย่างยิ่งในคลาส Activity
หลักของแอปพลิเคชันของคุณเป็นบล็อกการเริ่มต้นแบบคงที่ ตัวอย่างเช่น:
class MainActivity : AppCompatActivity () {
companion object {
init {
Security .removeProvider( " BC " ) // remove old/legacy Android-provided BC provider
Security .addProvider( BouncyCastleProvider ()) // add 'real'/correct BC provider
}
}
// ... etc ...
}
โปรดสังเกตว่าการประกาศการขึ้นต่อกันของ JJWT ข้างต้นทั้งหมดมีการขึ้นต่อกันของเวลาคอมไพล์เพียงครั้งเดียวเท่านั้น และส่วนที่เหลือจะถูกประกาศเป็นการขึ้นต่อกัน ของรันไทม์
เนื่องจาก JJWT ได้รับการออกแบบเพื่อให้คุณพึ่งพาเฉพาะ API ที่ออกแบบมาอย่างชัดเจนเพื่อให้คุณใช้ในแอปพลิเคชันของคุณ และรายละเอียดการใช้งานภายในอื่นๆ ทั้งหมดซึ่งสามารถเปลี่ยนแปลงได้โดยไม่มีคำเตือน - จะถูกลดระดับลงไปสู่การขึ้นต่อกันเฉพาะรันไทม์เท่านั้น นี่เป็นจุดสำคัญอย่างยิ่งหากคุณต้องการให้แน่ใจว่าการใช้งาน JJWT และการอัพเกรดมีเสถียรภาพเมื่อเวลาผ่านไป:
JJWT รับประกันความเข้ากันได้ของเวอร์ชันเชิงความหมายสำหรับอาร์ติแฟกต์ทั้งหมด ยกเว้น |
สิ่งนี้ทำเพื่อประโยชน์ของคุณ: การดูแลจัดการ jjwt-api
.jar เป็นอย่างดี และให้แน่ใจว่ามันมีสิ่งที่คุณต้องการและยังคงเข้ากันได้แบบย้อนหลังให้มากที่สุดเท่าที่จะเป็นไปได้ เพื่อให้คุณสามารถพึ่งพาสิ่งนั้นได้อย่างปลอดภัยด้วยขอบเขตการคอมไพล์ กลยุทธ์รันไทม์ jjwt-impl
.jar ช่วยให้นักพัฒนา JJWT มีความยืดหยุ่นในการเปลี่ยนแปลงแพ็กเกจภายในและการนำไปใช้งานเมื่อใดก็ตามที่จำเป็น สิ่งนี้ช่วยให้เราปรับใช้คุณสมบัติ แก้ไขข้อบกพร่อง และจัดส่งรุ่นใหม่ถึงคุณได้อย่างรวดเร็วและมีประสิทธิภาพยิ่งขึ้น
ความซับซ้อนส่วนใหญ่ซ่อนอยู่หลังอินเทอร์เฟซที่อิงตามตัวสร้างที่สะดวกและอ่านง่าย เหมาะสำหรับการอาศัยการเติมข้อมูลอัตโนมัติของ IDE เพื่อเขียนโค้ดอย่างรวดเร็ว นี่คือตัวอย่าง:
import io . jsonwebtoken . Jwts ;
import io . jsonwebtoken . security . Keys ;
import java . security . Key ;
// We need a signing key, so we'll create one just for this example. Usually
// the key would be read from your application configuration instead.
SecretKey key = Jwts . SIG . HS256 . key (). build ();
String jws = Jwts . builder (). subject ( "Joe" ). signWith ( key ). compact ();
มันง่ายขนาดนั้นเลยเหรอ!?
ในกรณีนี้ เราคือ:
การสร้าง JWT ซึ่งจะมีการตั้ง sub
การอ้างสิทธิ์ย่อยที่ลงทะเบียน (หัวเรื่อง) เป็น Joe
เราก็เป็นอย่างนั้น
การลงนาม JWT โดยใช้คีย์ที่เหมาะสมสำหรับอัลกอริทึม HMAC-SHA-256 ในที่สุดเราก็เป็น
กระชับ ให้เป็นรูปแบบ String
สุดท้าย JWT ที่ลงนามแล้วเรียกว่า 'JWS'
ผลลัพธ์ jws
String จะเป็นดังนี้:
EYJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJKb2UifQ.1KP0SsvENi7Uz1oQc07aXTL7kpQG5jBNIybqr60AlD4
ตอนนี้เรามาตรวจสอบ JWT กัน (คุณควรละทิ้ง JWT ที่ไม่ตรงกับลายเซ็นที่คาดหวังไว้เสมอ):
assert Jwts . parser (). verifyWith ( key ). build (). parseSignedClaims ( jws ). getPayload (). getSubject (). equals ( "Joe" );
มีสองสิ่งที่เกิดขึ้นที่นี่ key
จากก่อนหน้านี้ถูกใช้เพื่อตรวจสอบลายเซ็นของ JWT หากล้มเหลวในการตรวจสอบ JWT จะมีการส่ง SignatureException
(ซึ่งขยาย JwtException
) ออกไป สมมติว่า JWT ได้รับการตรวจสอบแล้ว เราจะแยกวิเคราะห์การอ้างสิทธิ์และยืนยันว่าหัวข้อนั้นถูกตั้งค่าเป็น Joe
คุณต้องรักการเขียนโค้ดแบบ one-liners ที่อัดแน่นไปด้วย!
บันทึก | JWT แบบปลอดภัย: หากต้องการรับผลลัพธ์ JWT |
แต่จะเกิดอะไรขึ้นหากการแยกวิเคราะห์หรือการตรวจสอบความถูกต้องของลายเซ็นล้มเหลว คุณสามารถจับ JwtException
และตอบสนองตาม:
try {
Jwts . parser (). verifyWith ( key ). build (). parseSignedClaims ( compactJws );
//OK, we can trust this JWT
} catch ( JwtException e ) {
//don't trust the JWT!
}
ตอนนี้เราได้ 'รสชาติ' การเริ่มต้นอย่างรวดเร็วของวิธีสร้างและแยกวิเคราะห์ JWT แล้ว เรามาพูดถึง API ของ JJWT ในเชิงลึกกันดีกว่า
คุณสร้าง JWT ดังนี้:
ใช้เมธอด Jwts.builder()
เพื่อสร้างอินสแตนซ์ JwtBuilder
คุณสามารถเลือกตั้งค่าพารามิเตอร์ header
ได้ตามต้องการ
วิธีการสร้างการเรียกเพื่อตั้งค่าเนื้อหาเพย์โหลดหรือการอ้างสิทธิ์
ทางเลือกในการเรียกเมธอด signWith
หรือ encryptWith
หากคุณต้องการเซ็นชื่อแบบดิจิทัลหรือเข้ารหัส JWT
เรียกใช้เมธอด compact()
เพื่อสร้างสตริง JWT ขนาดกะทัดรัดที่ได้
ตัวอย่างเช่น:
String jwt = Jwts . builder () // (1)
. header () // (2) optional
. keyId ( "aKeyId" )
. and ()
. subject ( "Bob" ) // (3) JSON Claims, or
//.content(aByteArray, "text/plain") // any byte[] content, with media type
. signWith ( signingKey ) // (4) if signing, or
//.encryptWith(key, keyAlg, encryptionAlg) // if encrypting
. compact (); // (5)
payload
JWT อาจเป็นเนื้อหา byte[]
(ผ่าน content
) หรือ การอ้างสิทธิ์ JSON (เช่น subject
, claims
ฯลฯ ) แต่ไม่ใช่ทั้งสองอย่าง
อาจใช้ลายเซ็นดิจิทัล ( signWith
) หรือการเข้ารหัส ( encryptWith
) ได้ แต่ไม่ใช่ทั้งสองอย่าง
JWT ที่ไม่มีการป้องกัน : หากคุณไม่ได้ใช้เมธอด |
ส่วนหัว JWT คือ Object
JSON ที่ให้ข้อมูลเมตาเกี่ยวกับเนื้อหา รูปแบบ และการดำเนินการเข้ารหัสใดๆ ที่เกี่ยวข้องกับ payload
JWT JJWT จัดเตรียมหลายวิธีในการตั้งค่าส่วนหัวทั้งหมดและ/หรือพารามิเตอร์ส่วนหัวแต่ละรายการหลายรายการ (คู่ชื่อ/ค่า)
วิธีที่ง่ายที่สุดและแนะนำในการตั้งค่าพารามิเตอร์ส่วนหัว JWT อย่างน้อยหนึ่งรายการ (คู่ชื่อ/ค่า) คือการใช้ตัวสร้าง header()
ของ JwtBuilder
ตามต้องการ จากนั้นเรียกใช้เมธอด and()
เพื่อกลับไปยัง JwtBuilder
สำหรับการกำหนดค่าเพิ่มเติม . ตัวอย่างเช่น:
String jwt = Jwts . builder ()
. header () // <----
. keyId ( "aKeyId" )
. x509Url ( aUri )
. add ( "someName" , anyValue )
. add ( mapValues )