שימוש ב- JWT עם OAuth באביב

1. סקירה כללית

במדריך זה נדון כיצד להשיג את יישום OAuth2 באבטחת האביב שלנו לשימוש בטוקני האינטרנט של JSON.

אנו ממשיכים לבנות בנוסף למאמר האביב REST API + OAuth2 + Angular בסדרת OAuth זו.

2. שרת ההרשאה OAuth2

בעבר, ערימת ה- OAuth של Spring Security הציעה אפשרות להגדיר שרת הרשאות כ- Spring Application. לאחר מכן היינו צריכים להגדיר אותו לשימוש JwtTokenStore כדי שנוכל להשתמש באסימונים של JWT.

עם זאת, מחסנית ה- OAuth הוצאה משימוש על ידי אביב ועכשיו נשתמש ב- Keycloak כשרת ההרשאה שלנו.

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

3. שרת משאבים

עכשיו, בואו נסתכל כיצד להגדיר את שרת המשאבים שלנו לשימוש ב- JWT.

נעשה זאת תוך application.yml קוֹבֶץ:

שרת: יציאה: 8081 servlet: path-context: / resource-server spring: security: oauth2: resourceserver: jwt: issuer-uri: // localhost: 8083 / auth / realms / baeldung jwk-set-uri: // localhost: 8083 / auth / realms / baeldung / protocol / openid-connect / certs

JWTs כוללים את כל המידע בתוך האסימון. אז שרת המשאבים צריך לאמת את חתימת האסימון כדי לוודא שהנתונים לא שונו. ה jwk-set-uri תכונה מכיל את המפתח הציבורי שהשרת יכול להשתמש בו למטרה זו.

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

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

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

בואו נראה כיצד נוכל להגדיר תמיכה ב- JWT באמצעות תצורת Java:

@Configuration מחלקה ציבורית SecurityConfig מרחיב את WebSecurityConfigurerAdapter {@Override מוגן חלל להגדיר (HttpSecurity http) זורק חריג {http.cors () .and () .authorizeRequests () .antMatchers (HttpMethod.GET, "/ user / info", "/ api / foos / ** ") .hasAuthority (" SCOPE_read ") .antMatchers (HttpMethod.POST," / api / foos ") .hasAuthority (" SCOPE_write ") .anyRequest () .uthuthicated () .and () .oauth2ResourceServer ( ) .jwt (); }}

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

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

4. תביעות בהתאמה אישית באסימון

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

בואו ניקח דוגמא לתביעה מותאמת אישית, אִרגוּן, שיכיל את שם ארגון המשתמש הנתון.

4.1. תצורת שרת הרשאה

לשם כך, עלינו להוסיף כמה תצורות לקובץ הגדרת התחום שלנו, baeldung-realm.json:

  • הוסף מאפיין אִרגוּן למשתמש שלנו [מוגן בדוא"ל]:
    "attributes": {"organization": "baeldung"},
  • הוסף protocolMapper שקוראים לו אִרגוּן אל ה jwtClient תְצוּרָה:
    "protocolMappers": [{"id": "06e5fc8f-3553-4c75-aef4-5a4d7bb6c0d1", "name": "organization", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-attribute -mapper "," consentRequired ": false," config ": {" userinfo.token.claim ":" true "," user.attribute ":" organization "," id.token.claim ":" true "," access.token.claim ":" true "," claim.name ":" organization "," jsonType.label ":" String "}},

לצורך הגדרת Keycloak עצמאית, ניתן לעשות זאת גם באמצעות מסוף הניהול.

בנוסף, חשוב לזכור זאת תצורת JSON לעיל היא ספציפית ל- Keycloak ויכולה להיות שונה עבור שרתי OAuth אחרים.

כאשר תצורה חדשה זו תפעל, נקבל מאפיין נוסף ארגון = באלדונג, במטען האסימון עבור [מוגן בדוא"ל]:

{jti: "989ce5b7-50b9-4cc6-bc71-8f04a639461e" exp: 1585242462 nbf: 0 iat: 1585242162 iss: "// localhost: 8083 / auth / realms / baeldung" sub: "a5461470-33eb-4b2d-82d4-b0 "typ:" Bearer "azp:" jwtClient "auth_time: 1585242162 session_state:" 384ca5cc-8342-429a-879c-c15329820006 "acr:" 1 "scope:" profile write קרא "ארגון:" baeldung "preferred_username:" [email protected ] "}

4.2. השתמש באסימון הגישה בלקוח Angular

לאחר מכן, נרצה להשתמש במידע האסימון ביישום הלקוח Angular שלנו. נשתמש בספריית angular2-jwt לשם כך.

נשתמש ב- אִרגוּן טענה שלנו AppService, והוסף פונקציה getOrganization:

getOrganization () {var token = Cookie.get ("access_token"); מטען מטען = this.jwtHelper.decodeToken (אסימון); this.organization = מטען מטען.ארגון; להחזיר את זה.ארגון; }

פונקציה זו עושה שימוש ב JwtHelperService מ ה angular2-jwt הספרייה כדי לפענח את אסימון הגישה ולקבל את התביעה המותאמת אישית שלנו. כעת כל שעלינו לעשות הוא להציג אותו בתוכנת שלנו AppComponent:

@Component ({selector: 'app-root', תבנית: "Spring Oauth Security - קוד הרשאה 

{{אִרגוּן}}

`}) ייצוא מחלקה AppComponent מיישם OnInit {ארגון ציבורי =" "; קונסטרוקטור (שירות פרטי: AppService) {} ngOnInit () {this.organization = this.service.getOrganization (); }}

5. גישה לתביעות נוספות בשרת המשאבים

אך כיצד נוכל לגשת למידע זה בצד שרת המשאבים?

5.1. גישה לתביעות שרת אימות

זה ממש פשוט: אנחנו רק צריכים לחלץ אותו מה- org.springframework.security.oauth2.jwt.Jwtשל אימות מנהל, כפי שהיינו עושים עבור כל מאפיין אחר ב UserInfoController:

@GetMapping ("/ user / info") מפה ציבורית getUserInfo (@AuthenticationPrincipal Jwt principal) {מפת מפה = Hashtable חדש (); map.put ("שם משתמש", principal.getClaimAsString ("שם משתמש מועדף")); map.put ("ארגון", principal.getClaimAsString ("ארגון")); החזר Collections.unmodifiableMap (מפה); } 

5.2. תצורה להוספה / הסרה / שינוי שם של תביעות

עכשיו, מה אם נרצה להוסיף תביעות נוספות בצד שרת המשאבים? או להסיר או לשנות שם של כמה?

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

כדי להשיג זאת, ראשית, נצטרך להוסיף מחלקה שמיישמת את מֵמִיר ממשק ושימושים MappedJwtClaimSetConverter להמיר תביעות:

מחלקה ציבורית OrganizationSubClaimAdapter מיישם ממיר {גמר פרטי MappedJwtClaimSetConverter הסופי = MappedJwtClaimSetConverter.withDefaults (Collections.emptyMap ()); המרת מפה ציבורית (תביעות מפה) {Map convertClaims = this.delegate.convert (תביעות); ארגון מחרוזות = convertClaims.get ("ארגון")! = Null? (מחרוזת) convertClaims.get ("ארגון"): "לא ידוע"; convertClaims.put ("organisation", organization.toUpperCase ()); החזר המרה תביעות; }}

שנית, שלנו אבטחהקונפיג בכיתה, אנחנו צריכים להוסיף משלנו JwtDecoder למשל לבטל את זה שמספק Spring Boot וקבע את שלנו OrganizationSubClaimAdapter כממיר התביעות שלה:

@Bean JwtDecoder הציבורי customDecoder (מאפייני OAuth2ResourceServerProperties) {NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withJwkSetUri (properties.getJwt (). GetJwkSetUri ()). Build (); jwtDecoder.setClaimSetConverter (חדש OrganizationSubClaimAdapter ()); החזר jwtDecoder; } 

עכשיו כשאנחנו מכה שלנו / משתמש / מידע ממשק API למשתמש [מוגן בדוא"ל], נקבל את אִרגוּן כפי ש לא ידוע.

שים לב כי עוקף את ברירת המחדל JwtDecoder שעועית שהוגדרה על ידי Spring Boot צריכה להיעשות בזהירות כדי להבטיח שכל התצורה הדרושה עדיין כלולה.

6. טעינת מפתחות מחברת Java Keystore

בתצורה הקודמת שלנו השתמשנו במפתח הציבורי המוגדר כברירת מחדל של שרת ההרשאה כדי לאמת את תקינות האסימון שלנו.

אנו יכולים גם להשתמש במקש המקשים ובאישור המאוחסנים בקובץ Java Keystore לביצוע תהליך החתימה.

6.1. צור קובץ JKS Java KeyStore

בואו ניצור תחילה את המפתחות - ובאופן ספציפי יותר א .jks קובץ - באמצעות כלי שורת הפקודה כלי מפתחות:

keytool -genkeypair -alias mytest -keyalg RSA -keypass mypass -keystore mytest.jks -storepass mypass

הפקודה תיצור קובץ שנקרא mytest.jks שמכיל את המפתחות שלנו - המפתחות הציבוריים והפרטיים.

וודאו גם מקש ו חלון ראווה אותו הדבר.

6.2. ייצא מפתח ציבורי

לאחר מכן, עלינו לייצא את המפתח הציבורי שלנו מ- JKS שנוצר, נוכל להשתמש בפקודה הבאה לשם כך:

keytool -list -rfc --keystore mytest.jks | openssl x509 -inform pem -pubkey

תגובה לדוגמא תיראה כך:

----- BEGIN מפתח ציבורי ----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgIK2Wt4x2EtDl41C7vfp OsMquZMyOyteO2RsVeMLF / hXIeYvicKr0SQzVkodHEBCMiGXQDz5prijTq3RHPy2 / 5WJBCYq7yHgTLvspMy6sivXN7NdYE7I5pXo / KHk4nz + Fa6P3L8 + L90E / 3qwf6j3 DKWnAgJFRY8AbSYXt1d5ELiIG1 / gEqzC0fZmNhhfrBtxwWXrlpUDT0Kfvf0QVmPR xxCLXT + tEe1seWGEqeOLL5vXRLqmzZcBe1RZ9kQQm43 + a9Qn5icSRnDfTAesQ3Cr lAWJKl2kcWU1HwJqw + dZRSZ1X4kEXNMyzPdPBbGmU6MHdhpywI7SKZT7mX4BDnUK eQIDAQAB ----- המפתח הציבורי END --- - ----- BEGIN CERTIFICATE ----- MIIDCzCCAfOgAwIBAgIEGtZIUzANBgkqhkiG9w0BAQsFADA2MQswCQYDVQQGEwJ1 czELMAkGA1UECBMCY2ExCzAJBgNVBAcTAmxhMQ0wCwYDVQQDEwR0ZXN0MB4XDTE2 MDMxNTA4MTAzMFoXDTE2MDYxMzA4MTAzMFowNjELMAkGA1UEBhMCdXMxCzAJBgNV BAgTAmNhMQswCQYDVQQHEwJsYTENMAsGA1UEAxMEdGVzdDCCASIwDQYJKoZIhvcN AQEBBQADggEPADCCAQoCggEBAICCtlreMdhLQ5eNQu736TrDKrmTMjsrXjtkbFXj Cxf4VyHmL4nCq9EkM1ZKHRxAQjIhl0A8 + aa4o06t0Rz8tv + ViQQmKu8h4Ey77KTM urIr1zezXWBOyOaV6Pyh5OJ8 / hWuj9y / Pi / dBP96sH + o9wylpwICRUWPAG0mF7dX eRC4iBtf4BKswtH2ZjYYX6wbccFl65aVA09Cn739EFZj0ccQi10 / rRHtbHlhhKnj i y + b10S6ps2XAXtUWfZEEJuN / mvUJ + YnEkZw30wHrENwq5QFiSpdpHFlNR8CasPn WUUmdV + JBFzTMsz3TwWxplOjB3YacsCO0imU + 5L + AQ51CnkCAwEAAaMhMB8wHQYD VR0OBBYEFOGefUBGquEX9Ujak34PyRskHk + WMA0GCSqGSIb3DQEBCwUAA4IBAQB3 1eLfNeq45yO1cXNl0C1IQLknP2WXg89AHEbKkUOA1ZKTOizNYJIHW5MYJU / zScu0 yBobhTDe5hDTsATMa9sN5CPOaLJwzpWV / ZC6WyhAWTfljzZC6d2rL3QYrSIRxmsp / J1Vq9WkesQdShnEGy7GgRgJn4A8CKecHSzqyzXulQ7Zah6GoEUD + vjb + BheP4aN hiYY1OuXD + HsdKeQqS + 7eM5U7WW6dz2Q8mtFJ5qAxjY75T0pPrHwZMlJUhUZ + Q2V FfweJEaoNB9w9McPe1cAiE + oeejZ0jq0el3 / dJsx3rlVqZN + lMhRJJeVHFyeb3XF lLFCUGhA7hxn2xf3x1JW ----- סיום תְעוּדָה-----

6.3. תצורת Maven

לאחר מכן, אנו לא רוצים שקובץ ה- JKS ייאסף על ידי תהליך הסינון של maven - לכן נדאג לא לכלול אותו ב pom.xml:

   src / main / resources true * .jks 

אם אנו משתמשים באביב אתחול, עלינו לוודא שקובץ ה- JKS שלנו מתווסף לנתיב הכיתה של היישום באמצעות התוסף Spring Boot Maven - addResources:

   org.springframework.boot spring-boot-maven-plugin נכון 

6.4. שרת הרשאה

כעת, נגדיר את Keycloak לשימוש ב- Keyboard שלנו מ- mytest.jks, על ידי הוספתו להגדרות הקובץ JSON של התחום KeyProvider סעיף כדלקמן:

{"id": "59412b8d-aad8-4ab8-84ec-e546900fc124", "name": "java-keystore", "providerId": "java-keystore", "subComponents": {}, "config": {" keystorePassword ": [" mypass "]," keyAlias ​​": [" mytest "]," keyPassword ": [" mypass "]," פעיל ": [" true "]," keystore ": [" src / main / resources /mytest.jks "]," עדיפות ": [" 101 "]," מופעלת ": [" נכון "]," אלגוריתם ": [" RS256 "]},

הנה קבענו את עדיפות ל 101, גדול יותר מכל זוג מקשים אחר עבור שרת ההרשאה שלנו, והגדר פָּעִיל ל נָכוֹן. זה נעשה על מנת להבטיח ששרת המשאבים שלנו יבחר את מקש המקשים המסוים הזה jwk-set-uri נכס שציינו קודם.

שוב, תצורה זו ספציפית ל- Keycloak ועשויה להיות שונה עבור יישומי שרת OAuth אחרים.

7. מסקנה

במאמר מהיר זה התמקדנו בהקמת פרויקט OAuth2 של Spring Security שלנו לשימוש ב- JSON Web Tokens.

ניתן למצוא את היישום המלא של מדריך זה ב- GitHub.