אימות Java טעון מחדש עם JSON Web Tokens (JWTs)

מתכוננים לבנייה, או נאבקים על אימות מאובטח ביישום Java שלך? לא בטוח מהיתרונות של שימוש באסימונים (ובמיוחד באסימוני אינטרנט של JSON), או כיצד יש לפרוס אותם? אני נרגש לענות על השאלות האלה ועוד, בשבילכם במדריך זה!

לפני שנצלול לתוך JSON Web Tokens (JWT), וספריית JJWT (שנוצרה על ידי ה- CTO של Stormpath, Les Hazlewood ומתוחזקת על ידי קהילת תורמים), בואו נסקור כמה יסודות.

1. אימות לעומת אימות אסימונים

מערך הפרוטוקולים שאפליקציה משתמשת בו כדי לאשר את זהות המשתמש הוא אימות. באופן מסורתי יישומים נמשכו בזהות באמצעות קובצי Cookie של הפעלות. פרדיגמה זו נשענת על אחסון בצד השרת של מזהי הפעלה שמאלץ מפתחים ליצור אחסון הפעלה שהוא ייחודי וספציפי לשרת, או מיושם כשכבת אחסון הפעלה נפרדת לחלוטין.

אימות אסימונים פותח כדי לפתור בעיות מזהי הפעלות בצד השרת לא, ולא הצליחו. בדיוק כמו אימות מסורתי, משתמשים מציגים אישורי אימות, אך הם מקבלים כעת סט של אסימונים במקום מזהה הפעלה. האישורים הראשוניים יכולים להיות זוג שם משתמש / סיסמה רגיל, מפתחות API, או אפילו אסימונים משירות אחר. (תכונת אימות המפתח של Stormpath היא דוגמה לכך.)

1.1. למה אסימונים?

בפשטות רבה, שימוש באסימונים במקום מזהי הפעלות יכול להוריד את עומס השרת שלך, לייעל את ניהול ההרשאות ולספק כלים טובים יותר לתמיכה בתשתית מבוזרת או מבוססת ענן. במקרה של JWT, זה מושג בעיקר באמצעות האופי חסר המדינה של סוגים אלה של אסימונים (עוד על כך בהמשך).

אסימונים מציעים מגוון רחב של יישומים, כולל: ערכות הגנה על זיוף Cross Site Request (CSRF), אינטראקציות OAuth 2.0, מזהי הפעלה ו (בעוגיות) כמייצגי אימות. ברוב המקרים, התקנים אינם מציינים פורמט מסוים עבור אסימונים. הנה דוגמה לאסימון CSRF טיפוסי של אבטחת אביב בצורה HTML:

אם אתה מנסה לפרסם טופס זה ללא אסימון ה- CSRF הנכון, תקבל תגובת שגיאה וזה השירות של אסימונים. הדוגמה שלעיל היא אסימון "טיפש". פירוש הדבר שאין שום משמעות אינהרנטית שיש ללקט מהאסימון עצמו. זה גם המקום בו JWTs עושים הבדל גדול.

2. מה יש ב- JWT?

JWTs (מבוטאים "jots") הם מחרוזות מוגנות URL, מקודדות, חתומות בקריפטוגרפיה (לפעמים מוצפנות) שיכולות לשמש אסימונים במגוון יישומים. הנה דוגמה ל- JWT המשמש כאסימון CSRF:

במקרה זה, אתה יכול לראות שהאסימון ארוך בהרבה מאשר בדוגמה הקודמת שלנו. בדיוק כמו שראינו קודם, אם הטופס מוגש ללא האסימון אתה מקבל תשובת שגיאה.

אז למה JWT?

האסימון הנ"ל חתום בקריפטוגרפיה ולכן ניתן לאמתו, ומספק הוכחה שלא טופלו בו. כמו כן, JWTs מקודדים עם מגוון מידע נוסף.

בואו נסתכל על האנטומיה של JWT כדי להבין טוב יותר איך אנחנו סוחטים ממנה את כל הטוב הזה. יתכן ששמת לב שיש שלושה חלקים נפרדים המופרדים לפי תקופות (.):

כּוֹתֶרֶתeyJhbGciOiJIUzI1NiJ9
מטעןeyJqdGkiOiJlNjc4ZjIzMzQ3ZTM0MTBkYjdlNjg3Njc4MjNiMmQ3MCIsImlhdC

I6MTQ2NjYzMzMxNywibmJmIjoxNDY2NjMzMzE3LCJleHAiOjE0NjY2MzY5MTd9

חֲתִימָהrgx_o8VQGuDa2AqCHSgVOD5G68Ld_YYM7N7THmvLIKc

כל קטע מקודד לכתובת URL של base64. זה מבטיח שניתן יהיה להשתמש בו בבטחה בכתובת אתר (על כך בהמשך). בואו נסתכל מקרוב על כל קטע בנפרד.

2.1. הכותרת

אם אתה base64 כדי לפענח את הכותרת, תקבל את מחרוזת JSON הבאה:

{"alg": "HS256"}

זה מראה כי ה- JWT נחתם עם HMAC באמצעות SHA-256.

2.2. המטען

אם אתה מפענח את המטען, תקבל את מחרוזת JSON הבאה (מעוצבת לבהירות):

{"jti": "e678f23347e3410db7e68767823b2d70", "iat": 1466633317, "nbf": 1466633317, "exp": 1466636917}

בתוך המטען, כפי שאתה יכול לראות, ישנם מספר מקשים עם ערכים. מפתחות אלה נקראים "תביעות" ובמפרט JWT יש שבעה מאלה שצוינו כטענות "רשומות". הם:

issמנפיק
תַתנושא
אודיקהל
expתפוגה
nbfלא לפני
אני בהונפק ב
jtiמזהה JWT

בעת בניית JWT, אתה יכול להגיש כל טענה מותאמת אישית שתרצה. הרשימה שלמעלה מייצגת בפשטות את הטענות שמורות הן במפתח המשמש והן בסוג הצפוי. ל- CSRF שלנו מזהה JWT, זמן "הונפק בשעה", זמן "לא לפני" וזמן תפוגה. זמן התפוגה הוא בדיוק דקה לאחר שהונפק בזמן.

2.3. החתימה

לבסוף, קטע החתימה נוצר על ידי לקיחת הכותרת והמטען יחד (עם. בין לבין) והעברתו דרך האלגוריתם שצוין (HMAC באמצעות SHA-256, במקרה זה) יחד עם סוד ידוע. שימו לב שהסוד הוא תמיד מערך בתים, וצריך להיות באורך הגיוני לאלגוריתם שבשימוש. להלן, אני משתמש במחרוזת מקודדת אקראית base64 (לקריאות) המומר למערך בתים.

זה נראה כך בפסאוד-קוד:

מחשב HMACSHA256 (כותרת + "." + מטען, base64DecodeToByteArray ("4pE8z3PBoHjnV1AhvGk + e8h2p + ShZpOnpr8cwHmMh1w ="))

כל עוד אתה יודע את הסוד, אתה יכול ליצור את החתימה בעצמך ולהשוות את התוצאה שלך לחלק החתימה של ה- JWT כדי לוודא שלא טופלו בה. מבחינה טכנית, JWT שנחתם בקריפטוגרפיה נקרא JWS. JWTs יכולים גם להיות מוצפנים ואז ייקראו JWE. (בפועל, המונח JWT משמש לתיאור JWE ו- JWS).

זה מחזיר אותנו ליתרונות השימוש ב- JWT כאסימון CSRF שלנו. אנו יכולים לאמת את החתימה ונוכל להשתמש במידע המקודד ב- JWT כדי לאשר את תקפותה. לכן, לא רק שייצוג המחרוזות של ה- JWT צריך להתאים למה שמאוחסן בצד השרת, אנחנו יכולים להבטיח שהוא לא יפוג פשוט על ידי בדיקת exp תְבִיעָה. זה חוסך מהשרת שמירה על מצב נוסף.

ובכן, כיסינו כאן הרבה אדמות. בואו נצלול לקוד כלשהו!

3. הגדר את מדריך JJWT

JJWT (//github.com/jwtk/jjwt) היא ספריית Java המספקת יצירה ואימות של JSON מקצה לקצה. לנצח קוד פתוח ופתוח (רישיון אפאצ'י, גרסה 2.0), הוא תוכנן עם ממשק ממוקד בונה שמסתיר את רוב מורכבותו.

הפעולות העיקריות בשימוש ב- JJWT כוללות בנייה וניתוח JWTs. נבדוק את הפעולות הללו בהמשך, ואז ניכנס לכמה תכונות מורחבות של JJWT, ולבסוף, נראה JWTs פועלים כאסימונים של CSRF ביישום Spring Security, Spring Boot.

הקוד המוצג בסעיפים הבאים ניתן למצוא כאן. הערה: הפרויקט משתמש באביב אתחול מההתחלה כיוון שהוא קל לאינטראקציה עם ה- API שהוא חושף.

כדי לבנות את הפרויקט, בצע את הפעולות הבאות:

שיבוט git //github.com/eugenp/tutorials.git מדריכי תקליטורים / jjwt mvn נקי להתקין

אחד הדברים הנהדרים באביב המגף הוא כמה קל להפעיל יישום. כדי להפעיל את היישום JJWT Fun, פשוט בצע את הפעולות הבאות:

java -jar יעד / *. צנצנת 

ישנן עשר נקודות קצה שנחשפו ביישום לדוגמה זה (אני משתמש ב- httpie כדי לקיים אינטראקציה עם היישום. ניתן למצוא אותו כאן).

http localhost: 8080
פקודות זמינות (מניח ש- httpie - //github.com/jkbrzt/httpie): http // localhost: 8080 / הודעת שימוש זו http // localhost: 8080 / סטטי-בונה לבנות JWT מתביעות בקוד קשה http POST // localhost: 8080 / תבנית דינמית-בונה-כללית -1 = ערך -1 ... [claim-n = value-n] בנה JWT מועבר בתביעות (באמצעות מפת תביעות כללית) http POST // localhost: 8080 / תביעה ספציפית לבונה דינמי -1 = value-1 ... [claim-n = value-n] לבנות JWT מועבר בתביעות (בשיטות תביעה ספציפיות) http POST // localhost: 8080 / dynamic-builder-compress claim-1 = value-1 ... [claim-n = value-n] build DEFLATE JWT דחוס מועבר בתביעות http // localhost: 8080 / מנתח? jwt = ניתוח עבר ב- JWT http // localhost: 8080 / parser-enforce? jwt = ניתוח עבר ב- JWT אוכפת את התביעה הרשומה 'iss' ואת התביעה המותאמת אישית 'hasMotorcycle' http // localhost: 8080 / get-secrets הצג את מפתחות החתימה הנמצאים בשימוש כרגע. http // localhost: 8080 / refresh-secrets צור מפתחות חתימה חדשים והראה אותם. http POST // localhost: 8080 / set-secrets HS256 = base64-encoded-value HS384 = base64-encoded-value HS512 = base64-encoded-value הגדירו במפורש סודות לשימוש ביישום.

בחלקים הבאים נבחן כל אחת מנקודות הקצה הללו ואת קוד JJWT הכלול במטפלים.

4. בניית JWTs עם JJWT

בגלל הממשק הרהוט של JJWT, יצירת ה- JWT היא בעצם תהליך בן שלושה שלבים:

  1. הגדרת הטענות הפנימיות של האסימון, כמו מנפיק, נושא, תפוגה ותעודת זהות.
  2. החתימה ההצפנתית של ה- JWT (מה שהופך אותו ל- JWS).
  3. הדחיסה של ה- JWT למחרוזת הבטוחה לכתובת אתר, על פי כללי הסידור הקומפקטי של JWT.

ה- JWT הסופי יהיה מחרוזת מקודדת בת שלושה חלקים, חתומה באלגוריתם החתימה שצוין, ומשתמשת במפתח המסופק. לאחר נקודה זו, האסימון מוכן לשיתוף עם הצד השני.

הנה דוגמה ל- JJWT בפעולה:

String jws = Jwts.builder () .setIssuer ("Stormpath") .setSubject ("msilverman") .claim ("name", "Micah Silverman") .claim ("scope", "admins") // שישי 24 ביוני 2016 15:33:42 GMT-0400 (EDT) .setIssuedAt (Date.from (Instant.ofEpochSecond (1466796822L))) // שבת 24 ביוני 2116 15:33:42 GMT-0400 (EDT). SetExpiration (Date.from (Instant.ofEpochSecond (4622470422L))) .signWith (SignatureAlgorithm.HS256, TextCodec.BASE64.decode ("Yn2kjibddFAWtnPJ2AFlL8WXmohJMCvigQggaEypa5E =")) .compact.

זה דומה מאוד לקוד שב- StaticJWTController.fixedBuilder שיטת פרויקט הקוד.

בשלב זה, כדאי לדבר על כמה אנטי-דפוסים הקשורים ל- JWT וחתמה. אם ראית אי פעם דוגמאות JWT בעבר, סביר להניח שנתקלת באחד מהתרחישים החתומים נגד תבניות אלה:

  1. .signWith (SignatureAlgorithm.HS256, "סוד" .getBytes ("UTF-8"))
  2. .signWith (SignatureAlgorithm.HS256, "Yn2kjibddFAWtnPJ2AFlL8WXmohJMCvigQggaEypa5E =". getBytes ("UTF-8"))
  3. .signWith (SignatureAlgorithm.HS512, TextCodec.BASE64.decode ("Yn2kjibddFAWtnPJ2AFlL8WXmohJMCvigQggaEypa5E ="))

כל אחד מה HS אלגוריתמי חתימת סוג לוקח מערך בתים. לבני אדם זה נוח לקרוא כדי לקחת מחרוזת ולהמיר אותה למערך בתים.

אנטי דפוס 1 לעיל מדגים זאת. זה בעייתי מכיוון שהסוד נחלש מכך שהוא קצר כל כך וזה לא מערך בתים בצורתו המקורית. לכן, כדי לשמור על קריאה, אנו יכולים לקודד את בסיס 64 למערך הבתים.

עם זאת, אנטי דפוס 2 לעיל לוקח את מחרוזת המקודד base64 וממיר אותו ישירות למערך בתים. מה שצריך לעשות זה לפענח את מחרוזת base64 בחזרה למערך הבתים המקורי.

מספר 3 לעיל מדגים זאת. אז למה זה גם אנטי דפוס? זו סיבה עדינה במקרה זה. שימו לב שאלגוריתם החתימה הוא HS512. מערך הבתים אינו האורך המרבי HS512 יכול לתמוך, מה שהופך אותו לסוד חלש יותר ממה שאפשר לאלגוריתם זה.

קוד הדוגמה כולל מחלקה שנקראת שירות חשאי המבטיח כי נעשה שימוש בסודות העוצמה הנכונה עבור האלגוריתם הנתון. בזמן הפעלת היישום, מערכת סודות חדשה נוצרת עבור כל אחד מאלגוריתמי HS. ישנן נקודות קצה לרענון הסודות וכן להגדרת המפורש של הסודות.

אם הפרויקט פועל כמתואר לעיל, בצע את הפעולות הבאות כך שדוגמאות ה- JWT שלהלן יתאימו לתגובות מהפרויקט שלך.

http POST localhost: 8080 / סט-סודות \ HS256 = "Yn2kjibddFAWtnPJ2AFlL8WXmohJMCvigQggaEypa5E =" \ HS384 = "VW96zL + tYlrJLNCQ0j6QPTp + d1q75n / Wa8LVvpWyG8pPZOP6AA5X7XOIlI90sDwx" \ = HS512 "cd + Pr1js + w2qfT2BoCD + tPcYp9LbjpmhSMEJqUob1mcxZ7 + Wmik4AYdjX + DlDjmE4yporzQ9tm7v3z / י + QbdYg =="

עכשיו אתה יכול להכות את / בונה סטטי נקודת סיום:

http // localhost: 8080 / סטטי-בונה

זה מייצר JWT שנראה כך:

eyJhbGciOiJIUzI1NiJ9. eyJpc3MiOiJTdG9ybXBhdGgiLCJzdWIiOiJtc2lsdmVybWFuIiwibmFtZSI6Ik1pY2FoIFNpbHZlcm1hbiIsInNjb3BlIjoiYWRtaW5zIjk2A kP0i_RvTAmI8mgpIkDFhRX3XthSdP-eqqFKGcU92ZIQ

עכשיו, הכה:

http //localhost:8080/parser?jwt=eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJTdG9ybXBhdGgiLCJzdWIiOiJtc2lsdmVybWFuIiwibmFtZSI6Ik1pY2FoIFNpbHZlcm1hbiIsInNjb3BlIjoiYWRtaW5zIiwiaWF0IjoxNDY2Nzk2ODIyLCJleHAiOjQ2MjI0NzA0MjJ9.kP0i_RvTAmI8mgpIkDFhRX3XthSdP-eqqFKGcU92ZIQ

בתגובה יש את כל הטענות שכללנו כשיצרנו את ה- JWT.

HTTP / 1.1 200 OK סוג תוכן: יישום / json; charset = UTF-8 ... {"jws": {"body": {"exp": 4622470422, "iat": 1466796822, "iss": "Stormpath "," name ":" Micah Silverman "," scope ":" admins "," sub ":" msilverman "}," header ": {" alg ":" HS256 "}," חתימה ":" kP0i_RvTAmI8mgpIkDFhRX3XthSdP-eqqFKGcU92ZIQ "}," status ":" SUCCESS "}

זו פעולת הניתוח, אליה ניכנס בחלק הבא.

עכשיו, בואו נגיע לנקודת קצה שלוקחת תביעות כפרמטרים ותבנה עבורנו JWT מותאם אישית.

http -v POST localhost: 8080 / דינמי-בונה-כללי iss = Stormpath sub = msilverman hasMotorcycle: = true

הערה: יש הבדל עדין בין ה- hasMotorcycle תביעה והשאר טענות. httpie מניח שפרמטרים של JSON הם מחרוזות כברירת מחדל. כדי להגיש JSON גולמי באמצעות httpie, אתה משתמש ב- := צורה ולא =. בלי זה הוא היה מגיש "HasMotorcycle": "נכון", וזה לא מה שאנחנו רוצים.

הנה הפלט:

POST / HTTP / 1.1 דינמי-בונה-כללי קבל: יישום / json ... {"hasMotorcycle": true, "iss": "Stormpath", "sub": "msilverman"} HTTP / 1.1 200 OK סוג תוכן: application / json; charset = UTF-8 ... { "JWT": "eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJTdG9ybXBhdGgiLCJzdWIiOiJtc2lsdmVybWFuIiwiaGFzTW90b3JjeWNsZSI6dHJ1ZX0.OnyDs-zoL3-rw1GaSl_KzZzHK9GoiNocu-YwZ_nQNZU", "מעמד": "הצלחה"} 

בואו נסתכל על הקוד המגבה נקודת קצה זו:

@RequestMapping (value = "/ dynamic-builder-general", method = POST) ציבורי JwtResponse dynamicBuilderGeneric (@RequestBody Map תביעות) זורק UnupportedEncodingException {String jws = Jwts.builder (). SetClaims (claims) .signWith (SignatureAlgor, secretService.getHS256SecretBytes ()) .compact (); להחזיר JwtResponse חדש (jws); }

שורה 2 מבטיחה שה- JSON הנכנס יומר אוטומטית למפת ג'אווה, וזה מאוד שימושי עבור JJWT מכיוון שהשיטה בקו 5 פשוט לוקחת את המפה ומגדירה את כל התביעות בבת אחת.

עד כמה שקוד זה הוא קצר יותר, אנו זקוקים למשהו ספציפי יותר בכדי להבטיח שהתביעות המועברות תקפות. משתמש ב .setClaims (תביעות מפה) השיטה שימושית כאשר אתה כבר יודע שהתביעות המיוצגות במפה תקפות. זה המקום שבו בטיחות הסוג של Java נכנסת לספריית JJWT.

עבור כל אחת מהתביעות הרשומות המוגדרות במפרט JWT, קיימת שיטת Java מקבילה ב- JJWT שלוקחת את הסוג הנכון למפרט.

בואו נקלע לנקודת סיום אחרת בדוגמה שלנו ונראה מה קורה:

http -v POST localhost: 8080 / דינמי-בונה ספציפי iss = Stormpath sub: = 5 hasMotorcycle: = true

שים לב שעברנו במספר שלם, 5, עבור טענת "המשנה". הנה הפלט:

POST / HTTP / 1.1 ספציפי לבנאי ספציפי: קבל: יישום / json ... {"hasMotorcycle": true, "iss": "Stormpath", "sub": 5} HTTP / 1.1 400 חיבור בקשה לא טובה: סגור תוכן- סוג: יישום / json; charset = UTF-8 ... {"exceptionType": "java.lang.ClassCastException", "message": "java.lang.Integer לא ניתן ללהק ל- java.lang.String", "status ":" שגיאה "}

כעת, אנו מקבלים תגובת שגיאה מכיוון שהקוד אוכף את סוג התביעות הרשומות. במקרה הזה, תַת חייב להיות מחרוזת. הנה הקוד המגבה את נקודת הקצה הזו:

@RequestMapping (value = "/ specific-builder specific", method = POST) JwtResponse public dynamicBuilderSpecific (@RequestBody Map claims) זורק UnupportedEncodingException {JwtBuilder builder = Jwts.builder (); claimss.forEach ((key, value) -> {switch (key) {case "iss": builder.setIssuer ((String) value); break; case "sub": builder.setSubject ((String) value); break ; מקרה "aud": builder.setAudience ((String) value); break; case "exp": builder.setExpiration (Date.from (Instant.ofEpochSecond (Long.parseLong (value.toString ())))); break ; מקרה "nbf": builder.setNotBefore (Date.from (Instant.ofEpochSecond (Long.parseLong (value.toString ()))); break; case "iat": builder.setIssuedAt (Date.from (Instant.ofEpochSecond) (Long.parseLong (value.toString ())))); הפסקה; מקרה "jti": builder.setId ((String) ערך); הפסקה; ברירת מחדל: builder.claim (מפתח, ערך);}}); builder.signWith (SignatureAlgorithm.HS256, secretService.getHS256SecretBytes ()); להחזיר JwtResponse חדש (builder.compact ()); }

בדיוק כמו בעבר, השיטה מקבלת א מַפָּה של טענות כפרמטר שלה. עם זאת, הפעם אנו קוראים לשיטה הספציפית לכל אחת מהתביעות הרשומות האוכפות את הסוג.

חידוד אחד לכך הוא להפוך את הודעת השגיאה ליותר ספציפית. כרגע, אנחנו רק יודעים שאחת הטענות שלנו איננה הסוג הנכון. איננו יודעים איזו תביעה הייתה בטעות או מה עליה להיות. הנה שיטה שתיתן לנו הודעת שגיאה ספציפית יותר. הוא עוסק גם בבאג בקוד הנוכחי.

חלל פרטי להבטיח סוג (מחרוזת רשומה תביעה, ערך אובייקט, סוג צפוי סוג) {בוליאני isCorrectType = expectType.isInstance (value) || expectType == Long.class && הערך מופע של מספר שלם; if (! isCorrectType) {String msg = "סוג צפוי:" + expectType.getCanonicalName () + "לתביעה רשומה: '" + registeredClaim + "', אך קיבל ערך:" + value + "מהסוג:" + value. getClass (). getCanonicalName (); לזרוק JwtException חדש (msg); }}

שורה 3 בודקת שהערך שעבר הוא מהסוג הצפוי. אם לא, א JwtException נזרק עם השגיאה הספציפית. בואו נסתכל על זה בפעולה על ידי ביצוע אותה שיחה שעשינו קודם:

http -v POST localhost: 8080 / ספציפי לבונה דינמי iss = Stormpath sub: = 5 hasMotorcycle: = true
POST / HTTP / 1.1 ספציפי לבונה / קבל: יישום / json ...User-Agent: HTTPie / 0.9.3 {"hasMotorcycle": true, "iss": "Stormpath", "sub": 5} HTTP / 1.1 400 חיבור בקשה לא טובה: סוג סוג תוכן: application / json; charset = UTF -8 ... {"exceptionType": "io.jsonwebtoken.JwtException", "message": "סוג צפוי: java.lang. מחרוזת לתביעה רשומה: 'sub', אך קיבלה ערך: 5 מהסוג: java.lang . שלם "," status ":" ERROR "}

כעת, יש לנו הודעת שגיאה מאוד ספציפית שאומרת לנו כי תַת הטענה היא זו בטעות.

בואו נחזור אל הבאג הזה בקוד שלנו. לסוגיה אין שום קשר לספריית JJWT. העניין הוא שמכשיר JSON ל- Java Object המובנה באביב אתחול הוא חכם מדי לטובתנו.

אם יש שיטה שמקבלת אובייקט Java, ממפה JSON ימיר באופן אוטומטי מספר שהועבר קטן או שווה ל 2,147,483,647 ל- Java מספר שלם. כמו כן, הוא ימיר באופן אוטומטי מספר שעבר גדול מ -2,147,483,647 ל- Java ארוך. בשביל ה אני ב, nbf, ו exp טענות של JWT, אנו רוצים שבדיקת ה- type type שלנו תעבור בין אם האובייקט הממופה הוא מספר שלם או ארוך. לכן יש לנו את הסעיף הנוסף לקביעת הערך שהועבר הוא הסוג הנכון:

 בוליאני isCorrectType = expectType.isInstance (value) || expectType == Long.class && הערך מופע של מספר שלם;

אם אנו מצפים ל- Long, אך הערך הוא מופע של מספר שלם, אנו עדיין אומרים שזה הסוג הנכון. מתוך הבנה מה קורה עם אימות זה, כעת אנו יכולים לשלב אותו בתוכנו dynamicBuilderSpecific שיטה:

@RequestMapping (value = "/ specific-builder specific", method = POST) JwtResponse public dynamicBuilderSpecific (@RequestBody Map claims) זורק UnupportedEncodingException {JwtBuilder builder = Jwts.builder (); claimss.forEach ((key, value) -> {switch (key) {case "iss": ensureType (key, value, String.class); builder.setIssuer ((String) value); break; case "sub": ensureType (מפתח, ערך, String.class); builder.setSubject ((String) ערך); break; מקרה "aud": ensureType (key, value, String.class); builder.setAudience ((String) value); break ; מקרה "exp": ensureType (מפתח, ערך, Long.class); builder.setExpiration (Date.from (Instant.ofEpochSecond (Long.parseLong (value.toString ())))); הפסקה; מקרה "nbf": ensureType (מפתח, ערך, Long.class); builder.setNotBefore (Date.from (Instant.ofEpochSecond (Long.parseLong (value.toString ())))); הפסקה; מקרה "iat": להבטיח Type (מפתח, ערך, Long.class); builder.setIssuedAt (Date.from (Instant.ofEpochSecond (Long.parseLong (value.toString ()))); break; case "jti": ensureType (key, value, String.class); builder .setId ((מחרוזת) ערך); הפסקה; ברירת מחדל: builder.claim (מפתח, ערך);}}); builder.signWith (SignatureAlgorithm.HS256, secretService.getHS256SecretBytes ()); להחזיר JwtResponse חדש (builder.compact ()); }

הערה: בכל קוד הדוגמה בסעיף זה, JWTs נחתמים עם HMAC באמצעות אלגוריתם SHA-256. זאת על מנת שהדוגמאות יהיו פשוטות. ספריית JJWT תומכת ב -12 אלגוריתמי חתימה שונים שתוכלו לנצל בקוד שלכם.

5. ניתוח JWTs עם JJWT

ראינו קודם שלדוגמת הקוד שלנו יש נקודת קצה לניתוח JWT. להכות נקודת קצה זו:

http //localhost:8080/parser?jwt=eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJTdG9ybXBhdGgiLCJzdWIiOiJtc2lsdmVybWFuIiwibmFtZSI6Ik1pY2FoIFNpbHZlcm1hbiIsInNjb3BlIjoiYWRtaW5zIiwiaWF0IjoxNDY2Nzk2ODIyLCJleHAiOjQ2MjI0NzA0MjJ9.kP0i_RvTAmI8mgpIkDFhRX3XthSdP-eqqFKGcU92ZIQ

מייצר תגובה זו:

HTTP / 1.1 200 OK סוג תוכן: application / json; charset = UTF-8 ... {"claims": {"body": {"exp": 4622470422, "iat": 1466796822, "iss": "Stormpath "," name ":" מיכה סילברמן "," scope ":" admins "," sub ":" msilverman "}," header ": {" alg ":" HS256 "}," חתימה ":" kP0i_RvTAmI8mgpIkDFhRX3XthSdP-eqqFKGcU92ZIQ "}," status ":" SUCCESS "}

ה מנתח שיטת ה- StaticJWTController הכיתה נראית כך:

@RequestMapping (value = "/ parser", method = GET) מנתח JwtResponse ציבורי (@RequestParam String jwt) זורק Excodinged EncodingException {Jws jws = Jwts.parser (). SetSigningKeyResolver (secretService.getSigningKeyRes). להחזיר JwtResponse חדש (jws); }

שורה 4 מציינת כי אנו מצפים שהמחרוזת הנכנסת תהיה JWT חתום (JWS). בנוסף אנו משתמשים באותו סוד ששימש לחתימת ה- JWT בניתוחו. שורה 5 מנתחת את הטענות מה- JWT. באופן פנימי, הוא מאמת את החתימה וזה יגרום לחריג אם החתימה אינה חוקית.

שימו לב שבמקרה זה אנו עוברים ב SigningKeyResolver ולא מפתח עצמו. זהו אחד ההיבטים החזקים ביותר של JJWT. הכותרת של JWT מציינת את האלגוריתם המשמש לחתימה עליו. עם זאת, עלינו לאמת את ה- JWT לפני שאנו סומכים עליו. נראה שזה מלכוד 22. בואו נסתכל על ה- SecretService.getSigningKeyResolver שיטה:

פרטי SigningKeyResolver SigningKeyResolver = חדש SigningKeyResolverAdapter () {@Override בייט ציבורי [] resolSigningKeyBytes (כותרת JwsHeader, תביעות טוענות) {החזר TextCodec.BASE64.decode (secrets.get (header.getAlgorithm ())); }};

באמצעות הגישה ל- JwsHeader, אני יכול לבדוק את האלגוריתם ולהחזיר את מערך הבייט המתאים לסוד ששימש לחתימת ה- JWT. כעת, JJWT יוודא כי ה- JWT לא טופל באמצעות מערך בתים זה כמפתח.

אם אני מסיר את התו האחרון שהועבר ב- JWT (שהוא חלק מהחתימה), זו התגובה:

HTTP / 1.1 400 חיבור בקשה לא טובה: סוג סוג תוכן: יישום / json; charset = UTF-8 תאריך: שני, 27 ביוני 2016 13:19:08 GMT שרת: Apache-Coyote / 1.1 קידוד העברה: chunked {"exceptionType ":" io.jsonwebtoken.SignatureException "," message ":" חתימת JWT אינה תואמת לחתימה מחושבת מקומית. לא ניתן לקבוע תוקף JWT ואין לסמוך עליה. "," status ":" ERROR "}

6. JWTs בפועל: אבטחת CSRF באבטחת אביב

אמנם המוקד של פוסט זה אינו באביב ביטחוני, אך אנו הולכים להתעמק בו מעט כאן כדי להציג קצת שימוש בעולם האמיתי בספריית JJWT.

זיוף בקשות בין אתרים הוא פגיעות אבטחה לפיה אתר זדוני מתעתע בכם להגיש בקשות לאתר שעליו ביצעתם אמון. אחת התרופות הנפוצות לכך היא הטמעת דפוס סמלי סינכרון. גישה זו מכניסה אסימון לטופס האינטרנט ושרת היישומים בודק את האסימון הנכנס מול המאגר שלו כדי לאשר שהוא נכון. אם האסימון חסר או לא חוקי, השרת יגיב בשגיאה.

ל- Spring Security יש תבנית אסימון סינכרון מובנה. אפילו יותר טוב, אם אתה משתמש בתבניות Spring Boot ו- Thymeleaf, אסימון הסנכרון מוכנס אוטומטית עבורך.

כברירת מחדל, האסימון בו משתמש Spring Security הוא אסימון "מטומטם". זו רק סדרה של אותיות ומספרים. גישה זו בסדר גמור והיא עובדת. בחלק זה אנו משפרים את הפונקציונליות הבסיסית על ידי שימוש ב- JWT כאסימון. בנוסף לווידוא שהאסימון שהוגש הוא זה הצפוי, אנו מאמתים את ה- JWT כדי להוכיח עוד שלא טופלו באסימון וכדי להבטיח שהוא לא יפוג.

כדי להתחיל, אנו מתכוונים להגדיר את Spring Security באמצעות תצורת Java. כברירת מחדל, כל הנתיבים דורשים אימות וכל נקודות הקצה של POST דורשות אסימונים של CSRF. אנחנו הולכים להירגע בזה כדי שמה שבנינו עד כה עדיין יעבוד.

@Configuration בכיתה ציבורית WebSecurityConfig מרחיב את WebSecurityConfigurerAdapter {private String [] ignoreCsrfAntMatchers = {"/ dynamic-builder-compress", "/ dynamic-builder-general", "/ specific-builder specific", "/ set-secrets"}; התצורה הריקה מוגנת של @Override (HttpSecurity http) זורקת חריג {http .csrf () .ignoringAntMatchers (ignoreCsrfAntMatchers) .and (). AuthorizeRequests () .antMatchers ("/ **") .permitAll (); }}

אנחנו עושים כאן שני דברים. ראשית, אנו אומרים כי אסימוני CSRF הם לֹא נדרש בעת פרסום לנקודות הקצה של REST API שלנו (שורה 15). שנית, אנו אומרים כי יש לאפשר גישה לא מאומתת לכל הנתיבים (שורות 17 - 18).

בואו נאשר שאבטחת אביב פועלת כפי שאנחנו מצפים. הפעל את האפליקציה והקש על כתובת האתר הזו בדפדפן שלך:

// localhost: 8080 / jwt-csrf-form

להלן תבנית Thymeleaf לתצוגה זו:

זוהי טופס בסיסי מאוד אשר יפורסם לאותה נקודת קצה בעת הגשתו. שימו לב כי אין התייחסות מפורשת לטוקני CSRF בצורה. אם תראה את המקור, תראה משהו כמו:

זה כל האישור שאתה צריך כדי לדעת כי Spring Security פועל וכי התבניות של Thymeleaf מכניסות אוטומטית את אסימון CSRF.

כדי להפוך את הערך ל- JWT, אנו נאפשר מותאם אישית CsrfTokenRepository. כך משתנה תצורת אבטחת האביב שלנו:

@Configuration בכיתה ציבורית WebSecurityConfig מרחיב את WebSecurityConfigurerAdapter {@Autowired CsrfTokenRepository jwtCsrfTokenRepository; התצורה הריקה המוגנת על ידי @Override (HttpSecurity http) זורקת חריג {http .csrf () .csrfTokenRepository (jwtCsrfTokenRepository) .ignoringAntMatchers (ignoreCsrfAntMatchers) .and (). AuthorizeRequests () .antMatchers ("/ **). }}

כדי לחבר זאת, אנו זקוקים לתצורה החושפת שעועית המחזירה את מאגר האסימונים המותאם אישית. הנה התצורה:

@Configuration בכיתה ציבורית CSRFConfig {@ SecretService SecretService אוטומטית; @Bean @ConditionalOnMissingBean CsrfTokenRepository ציבורי jwtCsrfTokenRepository () {להחזיר JWTCsrfTokenRepository חדש (secretService.getHS256SecretBytes ()); }}

והנה המאגר המותאם אישית שלנו (החלקים החשובים):

מחלקה ציבורית JWTCsrfTokenRepository מיישמת CsrfTokenRepository {יומן לוגר לוגרי סופי פרטי = LoggerFactory.getLogger (JWTCsrfTokenRepository.class); בית פרטי [] סוד; JWTCsrfTokenRepository ציבורי (בתים [] סוד) {this.secret = סוד; } @ ביטול CsrfToken ציבורי generateToken (בקשת HttpServletRequest) {String id = UUID.randomUUID (). ToString (). להחליף ("-", ""); תאריך עכשיו = תאריך חדש (); תאריך תפוגה = תאריך חדש (System.currentTimeMillis () + (1000 * 30)); // 30 שניות אסימון מחרוזת; נסה {token = Jwts.builder () .setId (id) .setIssuedAt (now) .setNotBefore (now) .setExpiration (exp) .signWith (SignatureAlgorithm.HS256, סודי). compact (); } לתפוס (UnsupportedEncodingException e) {log.error ("לא ניתן ליצור CSRf JWT: {}", e.getMessage (), e); אסימון = id; } להחזיר DefaultCsrfToken חדש ("X-CSRF-TOKEN", "_csrf", אסימון); } @Override public void saveToken (אסימון CsrfToken, HttpServletRequest בקשה, HttpServletResponse תגובה) {...} @Override CsrfToken ציבורי loadToken (HttpServletRequest בקשה) {...}}

ה createToken השיטה יוצרת JWT שתוקפו יפוג 30 שניות לאחר יצירתו. עם צנרת זו במקום, אנו יכולים להדליק את היישום שוב ולהסתכל על המקור של / jwt-csrf-form.

כעת, השדה הנסתר נראה כך:

Huzzah! עכשיו אסימון ה- CSRF שלנו הוא JWT. זה לא היה קשה מדי.

עם זאת, זה רק חצי מהפאזל. כברירת מחדל, Spring Security פשוט שומר את אסימון ה- CSRF ומאשר שהאסימון שנשלח בטופס אינטרנט תואם לזה שנשמר. אנו רוצים להרחיב את הפונקציונליות כדי לאמת את ה- JWT ולוודא שלא פג תוקפו. לשם כך נוסיף מסנן. כך נראית כעת תצורת האבטחה שלנו באביב:

@Configuration בכיתה הציבורית WebSecurityConfig מרחיב את WebSecurityConfigurerAdapter {... @Override מוגן ריק להגדיר (HttpSecurity http) זורק חריג {http .addFilterAfter (חדש JwtCsrfValidatorFilter (), CsrfFilter.class) .csrfory. .and (). authorizeRequests () .antMatchers ("/ **") .permitAll (); } ...}

בשורה 9 הוספנו מסנן ואנחנו מכניסים אותו לשרשרת המסננים לאחר ברירת המחדל CsrfFilter. לכן, עד לפגיעה במסנן שלנו, אסימון JWT (בכללותו) כבר יאושר כערך הנכון שנשמר על ידי Spring Security.

הנה ה JwtCsrfValidatorFilter (זה פרטי מכיוון שהוא מעמד פנימי בתצורת האבטחה האביבית שלנו):

מחלקה פרטית JwtCsrfValidatorFilter מרחיב את OncePerRequestFilter {@Override מוגן חלל doFilterInternal (HttpServletRequest בקשה, HttpServletResponse תגובה, FilterChain filterChain) זורק ServletException, IOException {// הערה: יישום אמיתי לא צריך להיות ל- cken request.getAttribute ("_ csrf"); if (// אכפת רק אם זה POST "POST" .equals (request.getMethod ()) && // להתעלם אם נתיב הבקשה נמצא ברשימה שלנו Arrays.binarySearch (ignoreCsrfAntMatchers, request.getServletPath ()) <0 && / / ודא שיש לנו אסימון אסימון! = null) {// CsrfFilter כבר וודא שהאסימון תואם. // כאן, נוודא שלא פג תוקף נסה את {Jwts.parser () .setSigningKey (secret.getBytes ("UTF-8")) .parseClaimsJws (token.getToken ()); } לתפוס (JwtException e) {// ככל הנראה ExpiredJwtException, אך זה יטפל בכל request.setAttribute ("חריג", e); response.setStatus (HttpServletResponse.SC_BAD_REQUEST); משדר RequestDispatcher = request.getRequestDispatcher ("פג תוקף-jwt"); dispatcher.forward (בקשה, תגובה); }} filterChain.doFilter (בקשה, תגובה); }}

התבונן בשורה 23 ב. אנו מנתחים את ה- JWT כמו בעבר. במקרה זה, אם נזרק חריג, הבקשה תועבר ל פג תוקף-jwt תבנית. אם ה- JWT מאמת, העיבוד ממשיך כרגיל.

זה סוגר את הלולאה על עקיפת התנהגות האסימון המוגדרת כברירת מחדל של Spring Security CSRF עם מאגר ומאמת אסימונים JWT.

אם אתה מפעיל את האפליקציה, דפדף אל / jwt-csrf-form, המתן קצת יותר מ -30 שניות ולחץ על הכפתור, תראה משהו כזה:

7. תכונות מורחבות של JJWT

נסיים את מסע ה- JJWT שלנו במילה על כמה מהתכונות החורגות מהמפרט.

7.1. לאכוף תביעות

כחלק מתהליך הניתוח, JJWT מאפשר לך לציין טענות נדרשות וערכים שהתביעות הללו צריכות להיות. זה שימושי מאוד אם יש מידע מסוים ב- JWT שלך שחייב להיות נוכח כדי שתוכל להחשיב אותם כבעלי תוקף. זה נמנע מלוגיקה מסועפת רבה כדי לאמת ידנית טענות. הנה השיטה שמשרתת את / מנתח לאכוף נקודת קצה של פרויקט המדגם שלנו.

@RequestMapping (value = "/ parser-enforce", method = GET) ציבורי JwtResponse parserEnforce (@RequestParam String jwt) זורק UnupportedEncodingException {Jws jws = Jwts.parser () .requireIssuer ("Stormpath"). true) .setSigningKeyResolver (secretService.getSigningKeyResolver ()) .parseClaimsJws (jwt); להחזיר JwtResponse חדש (jws); }

שורות 5 ו -6 מראות לך את התחביר לתביעות רשומות וכן לתביעות מותאמות אישית. בדוגמה זו, ה- JWT ייחשב כפסול אם תביעת ה- ISS אינה קיימת או שאין לה ערך: Stormpath. זה יהיה גם לא חוקי אם התביעה המותאמת אישית של hasMotorcycle אינה קיימת או שאין לה את הערך: true.

בואו ניצור תחילה JWT שעוקב אחר הדרך המאושרת:

http -v POST localhost: 8080 / דינמי-בונה ספציפי \ iss = Stormpath hasMotorcycle: = true sub = msilverman
POST / HTTP / 1.1 ספציפי לבונה / קבל: יישום / json ... {"hasMotorcycle": true, "iss": "Stormpath", "sub": "msilverman"} HTTP / 1.1 200 OK Cache-Control: no-cache, שלא לאחסון, מקסימום בגיל = 0, חובה אימות מחדש Content-Type: application / json; charset = UTF-8 ... { "JWT": "eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJTdG9ybXBhdGgiLCJoYXNNb3RvcmN5Y2xlIjp0cnVlLCJzdWIiOiJtc2lsdmVybWFuIn0.qrH-U6TLSVlHkZdYuqPRDtgKNr1RilFYQJtJbcgwhR0", "מעמד ":" הצלחה "}

עכשיו, בואו נאמת את ה- JWT הזה:

http -v localhost: 8080 / מנתח-לאכוף JWT = eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJTdG9ybXBhdGgiLCJoYXNNb3RvcmN5Y2xlIjp0cnVlLCJzdWIiOiJtc2lsdmVybWFuIn0.qrH-U6TLSVlHkZdYuqPRDtgKNr1RilFYQJtJbcgwhR0?
? GET / מנתח-לאכוף JWT = http -v localhost: 8080 / מנתח-לאכוף JWT = eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJTdG9ybXBhdGgiLCJoYXNNb3RvcmN5Y2xlIjp0cnVlLCJzdWIiOiJtc2lsdmVybWFuIn0.qrH-U6TLSVlHkZdYuqPRDtgKNr1RilFYQJtJbcgwhR0 HTTP / 1.1 קבל:? * / * ... HTTP / 1.1 200 OK Cache-Control: לא- מטמון, ללא חנות, max-age = 0, חובה לאמת סוג תוכן: יישום / json; charset = UTF-8 ... {"jws": {"body": {"hasMotorcycle": true, "iss ":" Stormpath "," sub ":" msilverman "}," header ": {" alg ":" HS256 "}," חתימה ":" qrH-U6TLSVlHkZdYuqPRDtgKNr1RilFYQJtJbcgwhR0 "}," סטטוס ":" SUCCESS "}

בינתיים הכל טוב. עכשיו, הפעם, בואו נעזוב את ה- hasMotorcycle בחוץ:

http -v POST localhost: 8080 / דינמי-בונה ספציפי iss = Stormpath sub = msilverman

הפעם, אם ננסה לאמת את ה- JWT:

http -v localhost: 8080 / parser-enforce? jwt = eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJTdG9ybXBhdGgiLCJzdWIiOiJtc2lsdmVybWFuIn0.YMONlFY1cNaNcg

אנחנו מקבלים:

? GET / מנתח-לאכוף JWT = http -v localhost: 8080 / מנתח-לאכוף JWT = eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJTdG9ybXBhdGgiLCJzdWIiOiJtc2lsdmVybWFuIn0.YMONlFM1tNgttUYukDRsi9gKIocxdGAOLaJBymaQAWc HTTP / 1.1 קבל:? * / * ... HTTP / 1.1 400 Bad בקשה Cache-Control: אי-המטמון , ללא חנות, max-age = 0, חובה לאמת מחדש את החיבור: סוג תוכן סוג: יישום / json; charset = UTF-8 ... {"exceptionType": "io.jsonwebtoken.MissingClaimException", "message": "הצפי של הטענה של hasMotorcycle הוא: נכון, אך לא היה קיים בתביעות JWT.", "Status": "ERROR"}

זה מצביע על כך שתביעת hasMotorcycle שלנו הייתה צפויה, אך חסרה.

בואו נעשה דוגמה נוספת:

http -v POST localhost: 8080 / dynamic-builder-specific iss = Stormpath hasMotorcycle: = false false = msilverman

הפעם קיימת התביעה הנדרשת, אך יש לה ערך שגוי. בואו נראה את הפלט של:

http -v localhost: 8080 / מנתח-לאכוף JWT = eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJTdG9ybXBhdGgiLCJoYXNNb3RvcmN5Y2xlIjpmYWxzZSwic3ViIjoibXNpbHZlcm1hbiJ9.8LBq2f0eINB34AzhVEgsln_KDo-IyeM8kc-dTzSCr0c?
GET / מנתח-לאכוף JWT = http -v localhost:? 8080 / מנתח-לאכוף JWT = eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJTdG9ybXBhdGgiLCJoYXNNb3RvcmN5Y2xlIjpmYWxzZSwic3ViIjoibXNpbHZlcm1hbiJ9.8LBq2f0eINB34AzhVEgsln_KDo-IyeM8kc-dTzSCr0c HTTP / 1.1 קבל:? * / * ...HTTP / 1.1 400 בקרת מטמון בקשה רעה: ללא מטמון, ללא חנות, max-age = 0, חובה לאמת מחדש חיבור: סגור סוג תוכן: יישום / json; charset = UTF-8 ... {"exceptionType" : "io.jsonwebtoken.IncorrectClaimException", "message": "הצפוי hasMotorcycle לטעון שהוא: true, but was: false.", "status": "ERROR"}

זה מצביע על כך שתביעת ה- hasMotorcycle שלנו הייתה קיימת, אך היה לה ערך שלא היה צפוי.

MissingClaimException ו IncorrectClaimException הם החברים שלך בעת אכיפת תביעות ב- JWT שלך ותכונה שיש רק בספריית JJWT.

7.2. דחיסת JWT

אם יש לך הרבה טענות ל- JWT, הוא יכול להיות גדול - כל כך גדול, עד שהוא עשוי לא להשתלב בכתובת GET בחלק מהדפדפנים.

בואו נעשה JWT גדול:

http -v POST localhost: 8080 / דינמי-בונה ספציפי \ iss = מסלול סופה יש אופנוע: = תת אמיתי = מסילברמן = חום מהיר = שועל קפץ = מעל עצלן = כלב \ איפשהו = מעל קשת = דרך למעלה = גבוה ו = החלומות = חלמת = על

הנה ה- JWT המייצר:

eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJTdG9ybXBhdGgiLCJoYXNNb3RvcmN5Y2xlIjp0cnVlLCJzdWIiOiJtc2lsdmVybWFuIiwidGhlIjoicXVpY2siLCJicm93biI6ImZveCIsImp1bXBlZCI6Im92ZXIiLCJsYXp5IjoiZG9nIiwic29tZXdoZXJlIjoib3ZlciIsInJhaW5ib3ciOiJ3YXkiLCJ1cCI6ImhpZ2giLCJhbmQiOiJ0aGUiLCJkcmVhbXMiOiJ5b3UiLCJkcmVhbWVkIjoib2YifQ.AHNJxSTiDw_bWNXcuh-LtPLvSjJqwDvOOUcmkk7CyZA

הפראייר הזה גדול! עכשיו, בואו נקלע לנקודת סיום קצת שונה עם אותן טענות:

http -v POST localhost: 8080 / dynamic-builder-compress \ iss = Stormpath hasMotorcycle: = true sub = msilverman the = quick brown = fox jump = over עצלן = כלב \ איפשהו = over rainbow = way up = high and = the dreams = חלמת = על

הפעם, אנו מקבלים:

eyJhbGciOiJIUzI1NiIsImNhbGciOiJERUYifQ.eNpEzkESwjAIBdC7sO4JegdXnoC2tIk2oZLEGB3v7s84jjse_AFe5FOikc5ZLRycHQ3kOJ0Untu8C43ZigyUyoRYSH6_iwWOyGWHKd2Kn6_QZFojvOoDupRwyAIq4vDOzwYtugFJg1QnJv-5sY-TVjQqN7gcKJ3f-j8c-6J-baDFhEN_uGn58XtnpfcHAAD__w.3_wc-2skFBbInk0YAQ96yGWwr8r1xVdbHn-uGPTFuFE

62 תווים נמוכים יותר! הנה הקוד לשיטה בה משתמשים ליצירת ה- JWT:

@RequestMapping (value = "/ dynamic-builder-compress", method = POST) ציבורי JwtResponse dynamicBuildercompress (@RequestBody Map claims) זורק לא נתמך קידודException {String jws = Jwts.builder (). SetClaims (תביעות) .compressWith (CompressionCodecs.DEFL. .signWith (SignatureAlgorithm.HS256, secretService.getHS256SecretBytes ()) .compact (); להחזיר JwtResponse חדש (jws); }

שים לב בשורה 6 אנו מציינים אלגוריתם דחיסה לשימוש. זה כל מה שיש בזה.

מה לגבי ניתוח JWT דחוס? ספריית JJWT מזהה באופן אוטומטי את הדחיסה ומשתמשת באותו אלגוריתם לדחיסה:

GET /parser?jwt=eyJhbGciOiJIUzI1NiIsImNhbGciOiJERUYifQ.eNpEzkESwjAIBdC7sO4JegdXnoC2tIk2oZLEGB3v7s84jjse_AFe5FOikc5ZLRycHQ3kOJ0Untu8C43ZigyUyoRYSH6_iwWOyGWHKd2Kn6_QZFojvOoDupRwyAIq4vDOzwYtugFJg1QnJv-5sY-TVjQqN7gcKJ3f-j8c-6J-baDFhEN_uGn58XtnpfcHAAD__w.3_wc-2skFBbInk0YAQ96yGWwr8r1xVdbHn-uGPTFuFE HTTP / 1.1 קבל: * / * ... HTTP / 1.1 200 OK Cache-Control: no-cache, לא -store, max-age = 0, צריך לאמת מחדש את סוג התוכן: application / json; charset = UTF-8 ... {"claims": {"body": {"and": "the", "brown" : "fox", "dreamed": "of", "dreams": "you", "hasMotorcycle": true, "iss": "Stormpath", "jumped": "over", "lazy": "dog" , "rainbow": "way", "somewhere": "over", "sub": "msilverman", "the": "quick", "up": "high"}, "header": {"alg" : "HS256", "calg": "DEF"}, "חתימה": "3_wc-2skFBbInk0YAQ96yGWwr8r1xVdbHn-uGPTFuFE"}, "status": "SUCCESS"}

שימו לב ל קלג טענה בכותרת. זה מקודד אוטומטית ל- JWT והוא מספק רמז למנתח באיזה אלגוריתם להשתמש עבור לחץ לחץ.

הערה: המפרט של JWE תומך בדחיסה. במהדורה הקרובה של ספריית JJWT, אנו נתמוך ב- JWE ו- JWE דחוסים. אנו נמשיך לתמוך בדחיסה בסוגים אחרים של JWT, למרות שהיא לא מוגדרת.

8. כלים אסימונים עבור מכשירי Java

אמנם המוקד המרכזי של מאמר זה לא היה Spring Boot או Spring Security, אך באמצעות שתי הטכנולוגיות הללו ניתן היה להדגים את כל התכונות הנדונות במאמר זה. אתה אמור להיות מסוגל לבנות את השרת ולהתחיל לשחק עם נקודות הקצה השונות שדנו בהן. פשוט פגע:

http // localhost: 8080

Stormpath גם מתרגשת להביא מספר כלים למפתחי קוד פתוח לקהילת ג'אווה. אלו כוללים:

8.1. JJWT (על מה דיברנו)

JJWT הוא כלי קל לשימוש עבור מפתחים ליצור ולאמת JWT ב- Java. כמו ספריות רבות שתומכות ב- Stormpath, JJWT הוא קוד פתוח לחלוטין (רישיון אפאצ'י, גרסה 2.0), כך שכל אחד יכול לראות מה הוא עושה ואיך הוא עושה את זה. אל תהססו לדווח על בעיות כלשהן, להציע שיפורים ואף להגיש קוד כלשהו!

8.2. jsonwebtoken.io ו- java.jsonwebtoken.io

jsonwebtoken.io הוא כלי מפתח שיצרנו כדי להקל על פענוח JWTs. כל שעליך לעשות הוא להדביק JWT קיים בשדה המתאים לפענוח הכותרת, המטען והחתימה שלו. jsonwebtoken.io מופעל על ידי nJWT, ספריית JWT החינמית והפתוחה והנקייה ביותר (רישיון אפאצ'י, גרסה 2.0) עבור מפתחי Node.js. אתה יכול גם לראות קוד שנוצר עבור מגוון שפות באתר זה. האתר עצמו הוא קוד פתוח וניתן למצוא אותו כאן.

java.jsonwebtoken.io מיועד במיוחד לספריית JJWT. אתה יכול לשנות את הכותרות ואת המטען בתיבה הימנית העליונה, לראות את JWT שנוצר על ידי JJWT בתיבה השמאלית העליונה, ולראות דוגמה של קוד Java הבונה ומנתח בתיבות התחתונות. האתר עצמו הוא קוד פתוח וניתן למצוא אותו כאן.

8.3. מפקח JWT

הילד החדש על החסימה, JWT Inspector הוא סיומת קוד פתוח של Chrome המאפשרת למפתחים לבדוק ולפתור באגים ב- JWT ישירות בדפדפן. מפקח ה- JWT יגלה את ה- JWT באתר שלך (בעוגיות, באחסון מקומי / כותרות ובכותרות) ויהפוך אותם לנגישים בקלות דרך סרגל הניווט שלך ופאנל DevTools.

9. JWT זה למטה!

JWTs מוסיפים מעט אינטליגנציה לאסימונים רגילים. היכולת לחתום ולאמת באופן קריפטוגרפי, לבנות זמני תפוגה ולקודד מידע אחר ב- JWT קובעת את הבמה לניהול מושבים חסרי מדינה. יש לכך השפעה רבה על היכולת להגדיל את היישומים.

ב- Stormpath אנו משתמשים ב- JWT עבור אסימוני OAuth2, אסימונים של CSRF וקביעות בין מיקרו-שירותים, בין שאר השימושים.

ברגע שתתחיל להשתמש ב- JWT, ייתכן שלעולם לא תחזור לסימני העבר המטומטמים. יש שאלות כל שהן? הכה אותי ב- @afitnerd בטוויטר.


$config[zx-auto] not found$config[zx-overlay] not found