SpringClient WebClient ותמיכה ב- OAuth2

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

Spring Security 5 מספק תמיכה ב- OAuth2 לאי החסימה של Spring Webflux WebClient מעמד.

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

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

2. הגדרת התרחיש

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

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

  • שרת ההרשאה יהיה:
    • פועל בנמל 8081
    • לחשוף את / oauth / authorize,/ oauth / token ו oauth / check_token נקודות קצה לביצוע הפונקציונליות הרצויה
    • מוגדר עם משתמשים לדוגמא (למשל ג'ון/123) ולקוח OAuth יחיד (fooClientIdPassword/סוֹד)
  • שרת המשאבים יופרד משרת האימות ויהיה:
    • פועל בנמל 8082
    • מגישים פשוט פו משאב מאובטח אובייקט נגיש באמצעות / foos / {id} נקודת סיום

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

ה WebClient וכל הפונקציונליות התגובתית של Webflux הינה חלק מפרויקט Spring Security 5. לכן, נשתמש בעיקר במסגרת זו לאורך מאמר זה.

3. אבטחת אביב 5 מתחת למכסה המנוע

כדי להבין היטב את הדוגמאות העומדות לפנינו, טוב לדעת כיצד Spring Security מנהלת את תכונות ה- OAuth2 באופן פנימי.

מסגרת זו מציעה יכולות:

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

כמה מהמושגים הבסיסיים של עולם ה- OAuth2 של Spring Security מתוארים בתרשים הבא:

3.1. ספקים

אביב מגדיר את תפקיד ספק OAuth2, האחראי על חשיפת משאבים מוגנים OAuth 2.0.

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

3.2. רישומי לקוחות

א רישום לקוח הינה ישות המכילה את כל המידע הרלוונטי של לקוח ספציפי הרשום ספק OAuth2 (או ספק OpenID).

בתרחיש שלנו, זה יהיה הלקוח הרשום בשרת האימות, שזוהה על ידי bael-client-id תְעוּדַת זֶהוּת.

3.3. לקוחות מורשים

ברגע שמשתמש הקצה (המכונה גם בעל המשאבים) מעניק הרשאות ללקוח לגשת למשאביו, OAuth2AuthorizedClient הישות נוצרת.

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

3.4. מאגרים

יתר על כן, Spring Security מציע גם שיעורי מאגר לגישה לגופים שהוזכרו לעיל.

במיוחד, ה ReactiveClientRegistrationRepository וה ServerOAuth2AuthorizedClientRepository מחלקות משמשות בערימות תגובתיות, והן משתמשות באחסון בזיכרון כברירת מחדל.

Spring Boot 2.x יוצר שעועית של מחלקות המאגר הללו ומוסיף אותן אוטומטית להקשר.

3.5. שרשרת מסנני רשת אבטחה

אחד המושגים המרכזיים באביב ביטחון 5 הוא תגובתי SecurityWebFilterChain יֵשׁוּת.

כשמו כן הוא, הוא מייצג אוסף משורשר של WebFilter חפצים.

כאשר אנו מאפשרים את תכונות ה- OAuth2 ביישום שלנו, Spring Security מוסיף שני פילטרים לשרשרת:

  1. מסנן אחד מגיב לבקשות הרשאה ( / oauth2 / authorization / {registrationId} URI) או זורק א ClientAuthorizationRequiredException. הוא מכיל התייחסות ל ריאקטיבי לקוח רישום מאגר, והיא אחראית על יצירת בקשת ההרשאה להפניית סוכן המשתמש.
  2. המסנן השני שונה בהתאם לתכונה שאנו מוסיפים (יכולות OAuth2 לקוח או פונקציונליות התחברות OAuth2). בשני המקרים, האחריות העיקרית של מסנן זה היא ליצור את ה- OAuth2AuthorizedClient למשל ולאחסן אותו באמצעות ServerOAuth2AuthorizedClientRepository.

3.6. לקוח אינטרנט

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

הוא ישתמש בהם כדי להשיג את אסימון הגישה כדי להוסיף אותו אוטומטית לבקשה.

4. תמיכה באביב אבטחה 5 - זרימת אישורי הלקוח

Spring Security מאפשרת להגדיר את היישום שלנו כלקוח OAuth2.

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

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

4.1. תצורות לקוח וספק

כפי שראינו במאמר הכניסה של OAuth2, אנו יכולים להגדיר אותו בתכנות או להסתמך על התצורה האוטומטית של Spring Boot באמצעות מאפיינים להגדרת הרישום שלנו:

spring.security.oauth2.client.registration.bael.authorization-grant-type = client_credentials spring.security.oauth2.client.registration.bael.client-id = bael-client-id spring.security.oauth2.client.registration. bael.client-secret = bael-secret spring.security.oauth2.client.provider.bael.token-uri = // localhost: 8085 / oauth / token

כל אלה הם התצורות הדרושות לנו כדי לאחזר את המשאב באמצעות ה- אישורי לקוח זְרִימָה.

4.2. משתמש ב WebClient

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

לדוגמא, בואו נדמיין שיש לנו קרון עבודה שמנסה להשיג משאב מאובטח באמצעות WebClient ביישום שלנו:

@Client webClient פרטי מאושר; @Scheduled (fixedRate = 5000) logResourceServiceResponse () {webClient.get () .uri ("// localhost: 8084 / retrieve-resource"). Retrieve () .bodyToMono (String.class) .map (מחרוזת -> "אוחזר באמצעות אישורי לקוח סוג מענק:" + מחרוזת). מנוי (לוגר :: מידע); }

4.3. הגדרת התצורה של WebClient

לאחר מכן, בואו נקבע את webClient למשל שחיוונו אוטומטית במשימה המתוזמנת שלנו:

@Bean WebClient webClient (ReactiveClientRegistrationRepository clientRegistrations) {ServerOAuth2AuthorizedClientExchangeFilterFunction oauth = new ServerOAuth2AuthorizedClientExchangeFilterFunction (clientRegistrations, new UnAuthenticatedServerOAuth2AutorizedClient); oauth.setDefaultClientRegistrationId ("bael"); החזר את WebClient.builder () .filter (oauth) .build (); }

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

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

אחרת, נצטרך לציין זאת עד להגדרת הבקשה בעבודת cron:

webClient.get () .uri ("// localhost: 8084 / retrieve-resource") .attributes (ServerOAuth2AuthorizedClientExchangeFilterFunction .clientRegistrationId ("bael")). retrieve () // ...

4.4. בדיקה

אם אנו מריצים את היישום שלנו עם לנפות כאשר רמת רישום מופעלת, נוכל לראות את השיחות שאבטחת אביב עושה לנו:

oswrfclient.ExchangeFunctions: HTTP POST // localhost: 8085 / oauth / token oshttp.codec.json.Jackson2JsonDecoder: מפוענח [{access_token = 89cf72cd-183e-48a8-9d08-661584db4310, token_type_ bearer = bearer = 4 קרא (חתוך) ...] oswrfclient.ExchangeFunctions: HTTP GET // localhost: 8084 / retrieve-resource oscore.codec.StringDecoder: מפוענח "זהו המשאב!" c.b.w.c.service.WebClientChonJob: השגנו את המשאב הבא באמצעות אישורי לקוח סוג מענק: זהו המשאב!

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

5. תמיכה באבטחת אביב 5 - יישום באמצעות זרימת קוד ההרשאה

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

5.1. תצורות לקוח וספק

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

spring.security.oauth2.client.registration.bael.client-name = bael spring.security.oauth2.client.registration.bael.client-id = bael-client-id spring.security.oauth2.client.registration.bael. client-secret = bael-secret spring.security.oauth2.client.registration.bael .authorization-grant-type = permission_code spring.security.oauth2.client.registration.bael. redirect-uri = // localhost: 8080 / login / oauth2 / code / bael spring.security.oauth2.client.provider.bael.token-uri = // localhost: 8085 / oauth / token spring.security.oauth2.client.provider.bael .authorization-uri = // localhost: 8085 / oauth / authorize spring.security.oauth2.client.provider.bael.user-info-uri = // localhost: 8084 / user spring.security.oauth2.client.provider.bael.user-name-attribute = name

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

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

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

נקודת הקצה להפניה נוצרת אוטומטית על ידי Spring Security.

כברירת מחדל, כתובת ה- URL שהוגדרה עבורו היא / [action] / oauth2 / code / [registrationId], עם רק לְאַשֵׁר ו התחברות פעולות מותרות (על מנת למנוע לולאה אינסופית).

נקודת קצה זו אחראית על:

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

5.2. תצורות אבטחה HTTP

לאחר מכן, נצטרך להגדיר את SecurityWebFilterChain.

התרחיש הנפוץ ביותר הוא שימוש ביכולות הכניסה OAuth2 של Spring Security כדי לאמת משתמשים ולתת להם גישה לנקודות הקצה והמשאבים שלנו.

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

@Bean Security PublicWebFilterChain springSecurityFilterChain (ServerHttpSecurity http) {http.authorizeExchange () .any Exchange () .authenticated () .and () .oauth2Login (); החזר http.build (); }

5.3. הגדרת התצורה של WebClient

עכשיו הגיע הזמן להעמיד את שלנו WebClient למשל:

@Bean WebClient webClient (ReactiveClientRegistrationRepository clientRegistrations, ServerOAuth2AuthorisedClientRepository authorClients) {ServerOAuth2AuthorizedClientExchangeFilterFunction oauth = ServerOAuth2AuthorizedClientExchangeFilterFunction (clientRistrents); clientRistrents; oauth.setDefaultOAuth2AuthorizedClient (נכון); החזר את WebClient.builder () .filter (oauth) .build (); }

הפעם אנו מזריקים הן את מאגר רישום הלקוח והן את מאגר הלקוחות המורשה מההקשר.

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

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

בהמשך ננתח חלופות כדי לציין כי הלקוח ספציפי WebClient העסקה תשתמש.

5.4. משתמש ב WebClient

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

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

@RestController מחלקה ציבורית ClientRestController {@Autowired WebClient webClient; @GetMapping ("/ auth-code") מונו useOauthWithAuthCode () {מונו retrievedResource = webClient.get () .uri ("// localhost: 8084 / retrieve-resource"). Retrieve () .bodyToMono (String.class); להחזיר retrievedResource.map (מחרוזת -> "השגנו את המשאב הבא באמצעות Oauth:" + מחרוזת); }}

5.5. בדיקה

לבסוף, אנו נקרא לנקודת הקצה וננתח את המתרחש על ידי בדיקת רשומות היומן.

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

o.s.w.s.adapter.HttpWebHandlerAdapter: HTTP GET "/ auth-code" ... HTTP / 1.1 302 מיקום נמצא: / oauth2 / authorization / bael

היישום מפנה לנקודת הקצה של שירות ההרשאה לאימות באמצעות אישורים הקיימים ברשומות הספק (במקרה שלנו, נשתמש משתמש-בעל / סיסמת-בעל):

HTTP / 1.1 302 נמצא מיקום: // localhost: 8085 / oauth / authorize? Response_type = code & client_id = bael-client-id & state = ... & redirect_uri = http% 3A% 2F% 2Flocalhost% 3A8080% 2Flogin% 2Foauth2% 2Fcode% 2 פאבל

לאחר אימות, סוכן המשתמש נשלח חזרה ל- URI להפניה מחדש, יחד עם הקוד כפרמטר השאילתה וערך המצב שנשלח לראשונה (כדי למנוע התקפות CSRF):

o.s.w.s.adapter.HttpWebHandlerAdapter: HTTP GET "/ login / oauth2 / code / bael? code = ... & state = ...

היישום משתמש בקוד כדי להשיג אסימון גישה:

o.s.w.r.f.client.ExchangeFunctions: HTTP POST // localhost: 8085 / oauth / token

זה משיג מידע למשתמשים:

o.s.w.r.f.client.ExchangeFunctions: HTTP GET // localhost: 8084 / user

וזה מפנה את סוכן המשתמש לנקודת הקצה המקורית:

HTTP / 1.1 302 מיקום נמצא: / קוד אימות

לבסוף, שלנו WebClient מופע יכול לבקש את המשאב המאובטח בהצלחה:

o.s.w.r.f.client.ExchangeFunctions: HTTP GET // localhost: 8084 / retrieve-resource o.s.w.r.f.client.ExchangeFunctions: תגובה 200 OK o.s.core.codec.StringDecoder: מפוענח "זה המשאב!"

6. אלטרנטיבה - רישום לקוח בשיחה

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

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

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

מאז קישרנו את קֶרֶן עם לקוחות מורשים, אנו יכולים להשיג את OAuth2AuthorizedClient למשל באמצעות @ RegisteredOAuth2AuthorizedClient ביאור:

@GetMapping ("/ אות-קוד-הערה") מונו useOauthWithAuthCodeAndAnnotation (@ RegisteredOAuth2AuthorizedClient ("bael") OAuth2AuthorizedClient authorClient) {מונו retrievedResource = webClient.get () .ורי ("// localhost: 8084 / retour. 8084 / תכונות (ServerOAuth2AuthorizedClientExchangeFilterFunction.oauth2AuthorizedClient (authorisedClient)). retrieve () .bodyToMono (String.class); להחזיר retrievedResource.map (מחרוזת -> "משאב:" + מחרוזת + "- ראש המשויך:" + authorisedClient.getPrincipalName () + "- האסימון יפוג ב:" + authorisedClient.getAccessToken () .getExpiresAt ()); }

7. הימנעות מתכונות הכניסה של OAuth2

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

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

בתור התחלה, ורק כדי להיות ברורים בכל הלוח, אנחנו יכולים להשתמש ב- לְאַשֵׁר פעולה במקום התחברות אחד בעת הגדרת מאפיין URI להפניה מחדש:

spring.security.oauth2.client.registration.bael. redirect-uri = // localhost: 8080 / login / oauth2 / code / bael

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

כעת, נגדיר את SecurityWebFilterChain מבלי לכלול את oauth2Login ובמקום זאת נכלול את ה- oauth2Client אחד.

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

@Bean Security PublicWebFilterChain springSecurityFilterChain (ServerHttpSecurity http) {http.authorizeExchange () .anyExchange () .authenticated () .and () .oauth2Client () .and () .formLogin (); החזר http.build (); }

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

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

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

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

אנו יכולים לראות בתגובת נקודת הקצה כי הלקוח המורשה משויך הפעם למנהל בשם bael-client-id במקום ה משתמש בעל, נקרא על שם המשתמש שהוגדר בשירות האימות.

8. תמיכה במסגרות האביב - גישה ידנית

מחוץ לקופסא, אביב 5 מספק שיטת שירות אחת בלבד הקשורה ל- OAuth2 כדי להוסיף כותרת אסימון נושא לבקשה בקלות. זה ה HttpHeaders # setBearerAuth שיטה.

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

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

לקוח @Autowired WebClient; מונו ציבורי להשיגSecuredResource () {String encodedClientData = Base64Utils.encodeToString ("bael-client-id: bael-secret" .getBytes ()); משאב מונו = client.post () .uri ("localhost: 8085 / oauth / token") .header ("Authorization", "Basic" + encodedClientData) .body (BodyInserters.fromFormData ("grant_type", "client_credentials")) . retrieve () .bodyToMono (JsonNode.class) .flatMap (tokenResponse -> {String accessTokenValue = tokenResponse.get ("access_token") .textValue (); return client.get () .uri ("localhost: 8084 / retrieve-) משאב "). כותרות (h -> h.setBearerAuth (accessTokenValue)). retrieve () .bodyToMono (String.class);}); return resource.map (res -> "אחזר את המשאב באמצעות גישה ידנית:" + res); }

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

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

9. מסקנה

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

אחרון חביב, ניתחנו כיצד מנגנוני OAuth2 של Spring Security 5 פועלים מתחת למכסה המנוע כדי לעמוד במפרט OAuth2.

כמו תמיד, הדוגמה המלאה זמינה ב- Github.


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