אביב אבטחה ו- OpenID Connect

שים לב שמאמר זה עודכן למחסנית Spring Security OAuth 2.0 החדשה. ההדרכה באמצעות ערימת מדור קודם עדיין זמינה.

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

במדריך מהיר זה אנו נתמקד בהגדרת OpenID Connect (OIDC) עם Spring Security.

נציג היבטים שונים של מפרט זה, ואז נראה את התמיכה שמציעה Spring Security ליישם אותה על לקוח OAuth 2.0.

2. מבוא מהיר לחיבור OpenID

OpenID Connect הוא שכבת זהות הבנויה על גבי פרוטוקול OAuth 2.0.

לפיכך, חשוב מאוד להכיר את OAuth 2.0 לפני שתצלול ל- OIDC, במיוחד את זרימת קוד ההרשאה.

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

  • ליבה: אימות ושימוש בתביעות לתקשורת מידע על משתמשי קצה
  • גילוי: קובע כיצד לקוח יכול לקבוע באופן דינמי מידע על ספקי OpenID
  • רישום דינמי: מכתיב כיצד לקוח יכול להירשם אצל ספק
  • ניהול מושבים: מגדיר כיצד לנהל הפעלות OIDC

נוסף על כך, המסמכים מבחינים בין שרתי האימות של OAuth 2.0 המציעים תמיכה במפרט זה, ומתייחסים אליהם כ"ספקי OpenID "(OPs) ולקוחות ה- OAuth 2.0 המשתמשים ב- OIDC כ- Relying Parties (RPs). אנו מקפידים על מינוח זה במאמר זה.

כדאי לדעת כי לקוח יכול לבקש את השימוש בתוסף זה על ידי הוספת ה- פתוח היקף בבקשת האישור שלה.

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

עכשיו כן, אנחנו מוכנים לצלול עמוק יותר לעולם ה- OIDC.

3. הגדרת פרויקט

לפני שנתמקד בפיתוח בפועל, נצטרך לרשום לקוח OAuth 2.o אצל ספק OpenID שלנו.

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

ה- URI להפניה מחדש שהקמנו בתהליך זה הוא נקודת סיום בשירותנו: // localhost: 8081 / login / oauth2 / code / google.

עלינו להשיג זיהוי לקוח וסוד לקוח מתהליך זה.

3.1. תצורת Maven

נתחיל בהוספת תלות אלה לקובץ ה- pom של הפרויקט:

 org.springframework.boot spring-boot-starter-oauth2-client 2.2.6.RELEASE 

חפץ המתנע מצרף את כל התלות הקשורה ללקוח Spring Security, כולל:

  • ה אביב-אבטחה-oauth2-client תלות בפונקציונליות התחברות OAuth 2.0 ולקוח
  • ספריית JOSE לתמיכה ב- JWT

כרגיל, אנו יכולים למצוא את הגרסה האחרונה של חפץ זה באמצעות מנוע החיפוש Maven Central.

4. תצורה בסיסית באמצעות Boot Boot

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

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

קפיץ: אבטחה: oauth2: לקוח: רישום: google: client-id: client-secret: 

בואו נפעיל את היישום שלנו וננסה לגשת לנקודת קצה עכשיו. נראה כי אנו מנותבים לדף כניסה של Google עבור לקוח OAuth 2.0 שלנו.

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

בעבר, בפוסט התמיכה שלנו ב- WebClient ו- OAuth 2, ניתחנו את הפנימיות על האופן שבו Spring Security מטפל בשרתים ולקוחות OAuth 2.0.

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

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

אנחנו יכולים להסתכל על תצורות אלה ב- CommonOAuth2Provider enum.

עבור Google, הסוג הנמנה מגדיר מאפיינים כמו:

  • טווחי ברירת המחדל שישמשו
  • נקודת הסיום של ההרשאה
  • נקודת הסיום של אסימון
  • נקודת הקצה של UserInfo, שהיא גם חלק ממפרט הליבה של OIDC

4.1. גישה למידע המשתמשים

Spring Security מציעה ייצוג שימושי של מנהל משתמש שרשום אצל ספק OIDC, ה- OidcUser יֵשׁוּת.

מלבד הבסיסי OAuth2AuthenticatedPrincipal שיטות, ישות זו מציעה פונקציונליות שימושית:

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

אנו יכולים לגשת בקלות ליישות זו בבקר:

@GetMapping ("/ oidc-principal") OidcUser ציבורי getOidcUserPrincipal (@AuthenticationPrincipal OidcUser מנהל) {return return; }

או באמצעות SecurityContextHolder בשעועית:

אימות אימות = SecurityContextHolder.getContext (). GetAuthentication (); if (authentication.getPrincipal () instance of OidcUser) {OidcUser principal = ((OidcUser) authentication.getPrincipal ()); // ...}

אם נבדוק את המנהל, נראה כאן הרבה מידע שימושי, כמו שם המשתמש, דוא"ל, תמונת פרופיל ומיקום.

יתר על כן, חשוב לציין כי ספרינג מוסיף רשויות למנהל על פי היקפים שקיבלה מהספק, עם קידומת "תְחוּם_“. לדוגמא, ה פתוח היקף הופך ל SCOPE_openid הוענקה סמכות.

ניתן להשתמש ברשויות אלה להגבלת גישה למשאבים מסוימים, למשל:

@EnableWebSecurity המחלקה הציבורית MappedAuthorities מרחיב את WebSecurityConfigurerAdapter {מוגן חלל מוגדר (HttpSecurity http) {http .authorizeRequests (authorizeRequests -> authorizeRequests .mvcMatchers ("/ my-endpoint") .hasAuthority ("SCOPE_openid.). ; }}

5. OIDC בפעולה

עד כה למדנו כיצד אנו יכולים ליישם בקלות פתרון כניסה של OIDC באמצעות Spring Security

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

אבל האמת היא שעד כה לא היינו צריכים להתמודד עם שום היבט ספציפי ל- OIDC. המשמעות היא שאביב עושה את רוב העבודה בשבילנו.

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

5.1. תהליך הכניסה

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

רישום: רמה: org.springframework.web.client.RestTemplate: DEBUG

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

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

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

למרות שהכל מעיד על כך שגוגל צריכה לאחזר את פּרוֹפִיל וה אימייל היקף - מכיוון שאנחנו משתמשים בהם בבקשת ההרשאה - ה- OP מאחזרת את עמיתיהם המותאמים אישית במקום זאת, //www.googleapis.com/auth/userinfo.email ו //www.googleapis.com/auth/userinfo.profileכך שאביב לא מכנה נקודת הסיום.

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

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

@Configuration מחלקה ציבורית OAuth2LoginSecurityConfig מרחיב את WebSecurityConfigurerAdapter {@Override מוגן חלל להגדיר (HttpSecurity http) זורק חריג {הגדר googleScopes = חדש HashSet (); googleScopes.add ("//www.googleapis.com/auth/userinfo.email"); googleScopes.add ("//www.googleapis.com/auth/userinfo.profile"); OidcUserService googleUserService = OidcUserService חדש (); googleUserService.setAccessibleScopes (googleScopes); http .authorizeRequests (authorizeRequests -> authorizeRequests .anyRequest (). מאומת ()) .oauth2Login (oauthLogin -> oauthLogin .userInfoEndpoint () .oidcUserService (googleUserService)); }}

ההבדל השני שנצפה הוא שיחה ל- UW של JWK Set. כפי שהסברנו בהודעה שלנו ב- JWS וב- JWK, זה משמש לאימות חתימת אסימון הזיהוי המעוצב ב- JWT.

לאחר מכן ננתח את אסימון הזהות בפירוט.

5.2. אסימון הזהות

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

כפי שאמרנו קודם, OidcUser הישות מכילה את התביעות הכלולות באסימון הזהות ואת האסימון המעוצב בפועל של JWT, שאותו ניתן לבדוק באמצעות jwt.io.

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

אנו יכולים לראות שאסימון הזיהוי כולל כמה תביעות חובה:

  • מזהה המנפיק מעוצב ככתובת אתר (למשל "//accounts.google.com“)
  • מזהה נושא, המהווה הפניה של משתמש הקצה שמופיע על ידי המנפיק
  • זמן התפוגה של האסימון
  • הזמן בו הוצא האסימון
  • הקהל, שיכיל את מזהה הלקוח OAuth 2.0 שהגדרנו

וגם תביעות סטנדרטיות רבות של OIDC כמו אלה שהזכרנו קודם (שֵׁם, אזור, תְמוּנָה, אימייל).

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

5.3. תביעות והיקפים

כפי שאנו יכולים לדמיין, התביעות שאוחזרו על ידי ה- OP תואמות את ההיקפים שהגדרנו (או Spring Security).

OIDC מגדיר כמה טווחים שניתן להשתמש בהם לבקשת התביעות שהוגדרו על ידי OIDC:

  • פּרוֹפִיל, שבאמצעותם ניתן לבקש תביעות ברירת מחדל לפרופיל (למשל שם, מועדף_שם,תְמוּנָה, וכו ')
  • אימייל, לגישה ל- אימייל ו אימייל_אומת טוען
  • כתובת
  • מכשיר טלפון, לבקשות מספר טלפון ו מספר_אומת טוען

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

6. תמיכה באביב לגילוי OIDC

כפי שהסברנו במבוא, OIDC כולל תכונות רבות ושונות מלבד מטרת הליבה שלו.

היכולות שאנחנו הולכים לנתח בסעיף זה ובהמשך הן אופציונליות ב- OIDC. לפיכך, חשוב להבין שאולי יש אופציות שאינן תומכות בהן.

המפרט מגדיר מנגנון גילוי עבור RP לגלות את ה- OP ולקבל מידע הדרוש לאינטראקציה איתו.

בקצרה, OPs מספקים מסמך JSON של מטא נתונים סטנדרטיים. המידע חייב להיות מוגש על ידי נקודת קצה ידועה של מיקום המנפיק, /.well- ידוע / openid-configuration.

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

אבל בואו נקפוץ ישר לדוגמא כדי לראות זאת בבירור.

נגדיר מנהג רישום לקוח למשל:

קפיץ: אבטחה: oauth2: לקוח: רישום: custom-google: client-id: client-secret: ספק: custom-google: issuer-uri: //accounts.google.com

כעת אנו יכולים להפעיל מחדש את היישום שלנו ולבדוק את יומני הרישום כדי לאשר שהיישום מתקשר ל- תצורת openid נקודת קצה בתהליך ההפעלה.

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

//accounts.google.com/.well-known/openid-configuration

אנו יכולים לראות, למשל, את נקודות ההרשאה, האסימון ו- UserInfo שהשירות צריך להשתמש בהן, והיקפי התמיכה הנתמכים.

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

7. ניהול מושבים של OpenID Connect

מפרט זה משלים את פונקציונליות הליבה על ידי הגדרת:

  • דרכים שונות לפקח על מצב הכניסה של משתמש הקצה ב- OP באופן שוטף, כך שה- RP יכול להתנתק ממשתמש הקצה שהתנתק מספק OpenID
  • האפשרות לרשום URI של יציאת RP עם ה- OP כחלק מרישום הלקוח, כדי לקבל הודעה כאשר משתמש הקצה מתנתק מה- OP
  • מנגנון להודיע ​​ל- OP שמשתמש הקצה התנתק מהאתר ואולי ירצה להתנתק גם מה- OP

מטבע הדברים, לא כל ה- OPs תומכים בכל הפריטים הללו, וחלק מהפתרונות הללו ניתנים ליישום רק ביישום חזיתי באמצעות User-Agent.

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

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

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

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

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

7.1. תצורת ספק ה- OpenID

במקרה זה, אנו מגדירים ומשתמשים במופע Okta כספק OpenID שלנו. לא נפרט על אופן יצירת המופע, אך נוכל לבצע את השלבים במדריך זה, וזכור כי נקודת הקצה של ברירת המחדל של Spring Security להתקשרות חזרה תהיה / login / oauth2 / code / okta.

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

קפיץ: אבטחה: oauth2: לקוח: רישום: okta: client-id: client-secret: ספק: okta: issuer-uri: //dev-123.okta.com

OIDC מציין שניתן לציין את נקודת הקצה של יציאת OP במסמך Discovery, כ- נקודת סיום_פגישה_ אֵלֵמֶנט.

7.2. ה LogoutSuccessHandler תְצוּרָה

לאחר מכן, נצטרך להגדיר את HttpSecurity לוגיקה של התנתקות על ידי מתן התאמה אישית LogoutSuccessHandler למשל:

תצורת הריקות המוגנת על ידי @Override (HttpSecurity http) זורקת חריג {http .authorizeRequests (authorizeRequests -> authorizeRequests .mvcMatchers ("/ home"). PermAll () .anyRequest (). מאומת ()). Oauth2Login (oauthLogin -> oauthLogin ()) .logout (logout -> logout .logoutSuccessHandler (oidcLogoutSuccessHandler ())); }

עכשיו בואו נראה איך נוכל ליצור LogoutSuccessHandler למטרה זו באמצעות שיעור מיוחד שמספק Spring Security, ה- OidcClientInitiatedLogoutSuccessHandler:

@ ClientRegistrationRepository ClientRegistrationRepository פרטי; פרטי התנתקות SuccesHandler oidcLogoutSuccessHandler () {OidcClientInitiatedLogoutSuccessHandler oidcLogoutSuccessHandler = חדש OidcClientInitiatedLogoutSuccessHandler (this.clientRegistrationRepository); oidcLogoutSuccessHandler.setPostLogoutRedirectUri (URI.create ("// localhost: 8081 / home")); החזר oidcLogoutSuccessHandler; }

כתוצאה מכך, נצטרך להגדיר URI זה כ- URI להפניה מחדש חוקי בחלונית התצורה של OP Client.

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

אז מה יקרה עכשיו?

לאחר שנכנס ליישום שלנו, נוכל לשלוח בקשה אל /להתנתק נקודת קצה הניתנת על ידי Spring Security.

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

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

8. מסקנה

לסיכום, במדריך זה למדנו רבות על הפתרונות שמציעה OpenID Connect וכיצד נוכל ליישם חלק מהם באמצעות Spring Security.

כמו תמיד, את כל הדוגמאות המלאות ניתן למצוא בריפו GitHub שלנו.