ככל שהטכנולוגיות האינטרנט מתקדמות, כך גם השיטות והפרוטוקולים שנועדו להגן עליהן. פרוטוקולי OAuth 2.0 ו-OpenID Connect התפתחו משמעותית בתגובה לאיומי אבטחה מתחדשים ולמורכבות הגוברת של יישומי אינטרנט. שיטות הזיהוי המסורתיות, שפעם היו יעילות, הופכות כיום למיושנות עבור יישומי Single Page Applications (SPAs) מודרניים, אשר מתמודדים עם אתגרי אבטחה חדשים. בהקשר זה, דפוס הארכיטקטורה Backend-For-Frontend (BFF) הופיע כפתרון מומלץ לארגון האינטראקציות בין SPAs לבין מערכות ה-backend שלהן, ומציע גישה בטוחה יותר וניתנת לניהול להזדהות ולניהול סשנים. מאמר זה בוחן לעומק את דפוס ה-BFF, ומדגים את יישומו המעשי באמצעות פתרון מינימלי שמיושם עם .NET ו-React. בסופו של דבר, תבינו היטב כיצד לנצל את דפוס ה-BFF לשיפור האבטחה והפונקציונליות של יישומי האינטרנט שלכם.
הקשר היסטורי
ההיסטוריה של OAuth 2.0 ו-OpenID Connect משקפת את ההתפתחות המתמשכת של טכנולוגיות האינטרנט. בואו נבחן מקרוב את הפרוטוקולים האלה ואת השפעתם על יישומי אינטרנט מודרניים.
הפרוטוקול OAuth 2.0, שהוצג ב-2012, הפך לסטנדרט מאומץ באופן נרחב לאישור גישה. הוא מאפשר ליישומים צד שלישי לקבל גישה מוגבלת למשאבי משתמשים מבלי לחשוף את נתוני ההזדהות של המשתמש ללקוח. OAuth 2.0 תומך במספר תהליכים, כל אחד מהם תוכנן להסתגל בצורה גמישה למגוון מקרים שימושיים.
בעיסוק ביסיס של OAuth 2.0, הפרוtokול הזה הופיע ב-2014, והוסיף פונקציות אוטENTיות חשובות. הוא מספק ליישומים הללו שיטה סטנדרטית כדי לאמת את הזהות של המשתמש ולקבל מידע בסיסי עליו דרך נקודת גישה סטנדרטית או על ידי רכישת טוקן מזהה במבנה JWT (JSON Web Token).
אבולוציית המודל האיום
עם השיפור הגדול והפופולריות של SPAs, המודל האיום עבור SPAs גם הוא השתנה. חורים בגיונך כמו פריצת שם מקום (XSS) והטעת בבקשות מקום נוספים (CSRF) הפכו למודל האיום הרחב יותר. מפני שSPAs לעיתים קרובות מתנהלות באינטראקציה עם השרת דרך APIs, זה הפך לחשוב מאד לאחסן בטיחותית ולהשתמש בטוקנים הגישה והמרענן.
בהתאם לדרישות הזמנים, הפרוtokולים OAuth וOpenID Connect ממשיכים להתפתח כדי להתאים לאתגרים החדשים שמתקבצים עם טכנולוגיות חדשות ולמספר גדל של האיומים. באותו זמן, ההתפתחות המודל האיום והשיפור במדיניות הבטיחות מאומרים שגישות הישנות כבר לא מרגישות מושלמות לדרישות הבטיחות המודרניות. כתוצאה מכך, הפרוtokול OpenID Connect מציע עכשיו את מערכת היכולות הרחבה, אך רבים מהם כבר הופכים או בקרוב יהיו נחשבים מושלמים ולעיתים לא בטוחים. המגוון הזה יוצר דיficulties עבור מפתחי הSPAs בבחירת הדרך הנכונ
במיוחד, הזרם הבלתי בר-מושג עכשיו יכול להיות מתיישן, ועבור כל סוג של לקוח, בין אם זה ספא, אפליקציית סלולרית או יישומה על השולחן העבודה, מומלץ בעצם להשתמש בזרם הקוד האותוריציונלי ביחד עם מפתח האמון לסחר בקוד (PKCE).
ביטחון של ספא יחידות פועלים מודרניים
למה ספא יחידות פועלים מודרניים עדיין נתפסים כפגינים, אפילו בזמן שמשתמשים בזרם הקוד האותוריציונלי ביחד עם PKCE? יש כמה תשובות לשאלה הזו.
רכילות בקוד ג 'יאסק립ס
ג'יאסק립ס הוא שפה תכנות חזקה שמשחקת תפקיד עיקרי בספא יחידות פועלים מודרניים (SPAs). אך היכולתיות הרחבה שלה והשירותים המוחיים שלהם מגדירים סיכון. ספא מודרני שנבנה על ספקים ושיבוטים כמו React, Vue או Angular משתמש בספקים ותלויות רבות. אתם יכולים לראות אותם בתיקיית node_modules
, ומספר התלויות האלה יכול להיות במאות או אפילו באלפים. כל אחת מהספקים האלה עשויה להכיל רכילות בעלי רמות שונות של קריטיות, ולמפתחים של ספא אין אפשרות לבדוק את הקוד של כל התלויות המשמשות. לעיתים קרובות לא אפילו אנשים מעוות אחר רשימת התלויות המלאה, כיוון שהן תלויות די אקסיגניות אחת בשניהם. אפילו אם הם מפתחים את הקוד שלהם במידה מושלמת של א
מקוד גנאי בעל הזכויות האלה יכול לגנוב מידע מעבר לעמודים, לתקשר עם ממשק היישומון, לשלוח בקשות אל הרקע, לגנוב מידע מאחסון מקומי (localStorage, IndexedDB) ואפילו להתחיל את ההתנהגויות המסויימות לאותה מעצמה, ולקבל את האקסיסטר טוקנים המקוריים באותו אופן בעזרת אותו קוד האישור והפקסי פלואו.
פגיעה Spectre
הפגיעה Spectre מנצלת תכונות הארכיטקטורה המודרנית של המעבדים כדי לגשת למידע שצריך להיות מבודד. פגיעות אלה מסוכנות במיוחד עבור SPAs.
ראשית, SPAs משתמשים בעלי הזכויות ברצף גבוה בגיוון כדי לנהל את מצב היישומון ולתקשר עם השרת. זה מגביר את ממשק ההתקף למקוד גנאי שיכול לנצל פגיעות Spectre. שנית, לעומת היישומון הרב-דף (MPAs), SPAs נדירות מתחילים מחדש, אז הדף והקוד הנטען ממשיך להיות פעילים למשך זמן רב. זה נותן לאתקים הרבה יותר זמן לבצע התקפים בעזרת קוד גנאי בעל הזכויות.
פגיעות Spectre מאפשרות לאתקים לגנוב את הטוקנים הנשארים בזיכרון של יישומון בעל הזכויות בעזרת העיתים, ולאפשר להם לגשת למשאבים מוגנים על ידי חיקוי של היישומון המקורי. היכולת לבצע יישומון מופרזת גם יכולה להיות נצרך לגניבת מידע על המשימה של המשתמש, מה שמאפשר לאתקים להמשיך את ה
לא ניתן לשלול את האפשרות של גילוי פגיעויות נוספות בדומה ל-Spectre בעתיד.
מה לעשות?
נסכם מסקנה ביניים חשובה. אפליקציות חדשות (SPA) התלויות במספר גדול של ספריות JavaScript צד שלישי ורצות בסביבה דפדפן על מכשירי המשתמשים, פועלות בסביבת תוכנה וחומרה שהמפתחים אינם יכולים לשלוט בה במלואה. לכן, עלינו לשקול אפליקציות כאלה כפגיעות מטבען.
בתגובה לאיומים המפורטים, יותר מומחים נוטים להימנע לחלוטין מהשארת טוקנים בדפדפן ולעצב את האפליקציה כך שטוקני גישה וריענון יושגו ויעובדו רק על ידי צד השרת של האפליקציה, ולעולם לא יועברו לצד הדפדפן. בהקשר של SPA עם backend, ניתן להשיג זאת באמצעות תבנית הארכיטקטורה Backend-For-Frontend (BFF).
תכנית האינטראקציה בין השרת ההרשאה (OP), הלקוח (RP) המיישם את תבנית ה-BFF, ו-API צד שלישי (שרת המשאבים) נראית כך:
השימוש בתבנית BFF להגנה על SPAs מציע מספר יתרונות. טוקני גישה וריענון נשמרים בצד השרת ולעולם לא מועברים לדפדפן, מה שמונע את גניבתם עקב פגיעויות. ניהול הסשן והטוקנים מתבצע בשרת, מה שמאפשר שליטה בטיחותית טובה יותר ואימות אמינות יותר. אפליקציית הלקוח מתקשרת עם השרת דרך ה-BFF, מה שמפשט את לוגיקת האפליקציה ומפחית את הסיכון להרצת קוד זדוני.
יישום תבנית Backend-For-Frontend על פלטפורמת .NET
לפני שימוש מעשי באמצעי בין משתמש לשרת (BFF) על הפלטפורמה .NET, בואו נחשוב על הרכיבים הנחוצים לזה ונתכנן את הפעולות שלנו. נניח שיש לנו כבר שרת OpenID Connect מורכב ואנחנו צריכים לפתח ספא (SPA) שיעבד עם שרת בסיס, להיות מוכנים לאמץ את האמצעי הזה ולארגן את האינטראקציה בין החלקים השרתי והקליני בעזרת הדפוס הזה.
לפי ה 文档 OAuth 2.0 for Browser-Based Applications, הדפוס הארכיטקטורלי BFF מנבא שהשרת יפעל כלוע של OpenID Connect, ישתמש בזרם האותורציה המקבילה עם PKCE עבור ההתעמת, ישיג ויאחסן את הציקים והטוקנים הגישה והמרחזה בצדו שלו ולעולם לא ישלח אותם לצד הספא בברור. הדפוס BFF גם מנבא על נוכחות של אפי בצד של השרת המרכזית של ארבעה קווי קישור עיקריים:
- Check Session: משורה על בדיקת סדרה ההתעמת המעורבת של המשתמש. בד "" כ נקראת מעבר ל SPA באמצעות API אינטראקציבי (fetch) ואם בהצלחה, הוא מחזיר מידע על המשתמש המעורב. כך שה SPA, שנוטל ממקור שלישי (לדוגמה, CDN), יכול לבדוק את מצב ההתעמת ולהמשיך את עבודתו עם המשתמש או להמשיך להגיע להתעמת בעזרת השרת OpenID Connect.
- כניסה: זה מותח תהליך האמת מזהה בשרת OpenID Connect. בדרך טיפוסית, אם ה SPA אינו מצליח לקבל מידע מובטח של המשתמש בשלב 1 דרך בדיקת ההפעלה, הוא מסייע את הדפדפן לכתובת זו, שאז יוצרת בקשה מלאה לשרת OpenID Connect ומסייעת את הדפדפן לשם.
- הכנסה: מקבל את הקוד הסיסמה ששולח השרת לאחר שלב 2 במקרה של האמת המזהה המוצלחת. יוצרת בקשה ישירה אל שרת OpenID Connect כדי להחליף את הקוד הסיסמה זו + מוכח הקוד המוצלח לשימושים וטוקנים חדשים. מותחת הפעלה מובטחה בצד הלקוח על ידי יצירת עוגיית ההתחברות למשתמש.
- היציאה: משמש לסגור את ההפעלה המובטחה. בדרך טיפוסית, ה SPA מסייע את הדפדפן לכתובת זו, שאז יוצרת בקשה לשים סגור לקצה ההפעלה על שרת OpenID Connect כדי לסגור את ההפעלה, בנוסף למחיקת ההפעלה בצד הלקוח ולעוגיית ההתחברות.
עכשיו בואו נבדוק את הכלים שהפלטפורמה .NET מעבירה מוכנה ונראה איך אנחנו יכולים להשתמש בהם לממש את דפוס ה BFF. הפלטפורמה .NET מעבירה את ערכת הנתונים Microsoft.AspNetCore.Authentication.OpenIdConnect
בעזרת NuGet, שהיא מימוש מוכן של לוקח ה OpenID Connect מוסיף על ידי Microsoft. הערכת הזו תומכת בדרך הזרימה המוצעת וב PKCE, והיא מוסיפה קו קצת /signin-oidc, שברגע הזה מיישמת את התפקיד של נקודת
לדוגמה מעשית להביאה במערכת, נבחן שרת בדיקה לאופן קישור פתוח OpenID Connect בסיס על ספריית Abblix OIDC Server. אך כל מה שמוזכר למטה נוגע לכל סרבט אחר, כולל סרבטים פומביים זמינים מפיסבוק, גוגל, אפל, וכל אחרים שמורגלים לספירת ההדיגרמה של הספקת הפתיחה OpenID Connect.
כדי ליישם את הSPA מצד הקצה הקדמי, נהיה משתמשים בספריית React, ומצד האחורי, נהיה משתמשים ב.NET WebAPI. זו אחת הערכות הטכנולוגיות המרכזיות האחרונות בזמן כתיבת המאמר הזה.
הרשימת הרכיבים והתאמות ביניהם נראית כך:
כדי לעבד את הדוגמאות מהמאמר הזה, תעבור גם להתקין את .NET SDK וNode.js. כל הדוגמאות במאמר זה נוצרו ובודקו בעזרת .NET 8, Node.js 22 וReact 18, שהיו די עדכניים בזמן כתיבת המאמר.
יצירת SPA קליני בReact עם חלק אחורי על בסיס .NET.
כדי ליצור במהירות אפליקציית לקוח, נוח להשתמש בתבנית מוכנה. עד גרסת .NET 7, ה-SDK הציע תבנית מובנית לאפליקציית .NET WebAPI ו-React SPA. למרבה הצער, תבנית זו הוסרה בגרסת .NET 8. זו הסיבה שהצוות של Abblix יצר תבנית משלו, הכוללת backend של .NET WebApi, frontend SPA המבוסס על ספריית React ו-TypeScript, שנבנה עם Vite. תבנית זו זמינה לציבור כחלק מחבילת Abblix.Templates
, וניתן להתקין אותה על ידי הרצת הפקודה הבאה:
dotnet new install Abblix.Templates
כעת נוכל להשתמש בתבנית בשם abblix-react
. בואו נשתמש בה כדי ליצור אפליקציה חדשה בשם BffSample
:
dotnet new abblix-react -n BffSample
פקודה זו יוצרת אפליקציה המורכבת מ-backend של .NET WebApi ולקוח React SPA. הקבצים הקשורים ל-SPA ממוקמים בתיקיית BffSample\ClientApp
.
לאחר יצירת הפרויקט, המערכת תבקש ממך להריץ פקודה להתקנת התלויות:
cmd /c "cd ClientApp && npm install"
פעולה זו נחוצה להתקנת כל התלויות הנדרשות לחלק הלקוח של האפליקציה. לצורך השקת פרויקט מוצלחת, מומלץ להסכים ולבצע פקודה זו על ידי הכנסת Y
(כן).
בואו נשנה מיד את מספר הפורט שעליו רצה אפליקציית BffSample
באופן מקומי ל-5003. פעולה זו אינה חובה, אך היא תקל על הגדרת ה-OpenID Connect server בהמשך. כדי לעשות זאת, פתחו את קובץ BffSample\Properties\launchSettings.json
, מצאו את הפרופיל בשם https
ושנו את ערך התכונה applicationUrl
ל-https://localhost:5003
.
בניסיון הבא, הוסף ערכת ניגון NuGet הממלאת את הלקוח של OpenID Connect על ידי ניווט לתוך תיבת BffSample
וביצוע הפקודה הבאה:
dotnet add package Microsoft.AspNetCore.Authentication.OpenIdConnect
ארגן שני תבניות אימות בשם Cookies
ו OpenIdConnect
בתוך היישום, ואת ההגדרות שלהם מקרינות מההגדרות של היישום. על מנת לעשות את זה, ערכו שינויים בקובץ BffSample\Program.cs
:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
// ******************* התחלה *******************
var configuration = builder.Configuration;
builder.Services
.AddAuthorization()
.AddAuthentication(options => configuration.Bind("Authentication", options))
.AddCookie()
.AddOpenIdConnect(options => configuration.Bind("OpenIdConnect", options));
// ******************** סיום ********************
var app = builder.Build();
והוסף את ההגדרות הנחוצות להתחברות לשרת OpenID Connect בקובץ BffSample\appsettings.json
:
{
// ******************* התחלה *******************
"Authentication": {
"DefaultScheme": "Cookies",
"DefaultChallengeScheme": "OpenIdConnect"
},
"OpenIdConnect": {
"SignInScheme": "Cookies",
"SignOutScheme": "Cookies",
"SaveTokens": true,
"Scope": ["openid", "profile", "email"],
"MapInboundClaims": false,
"ResponseType": "code",
"ResponseMode": "query",
"UsePkce": true,
"GetClaimsFromUserInfoEndpoint": true
},
// ******************** סיום ********************
"Logging": {
"LogLevel": {
"Default": "Information",
ובקובץ BffSample\appsettings.Development.json
:
{
// ******************* התחלה *******************
"OpenIdConnect": {
"Authority": "https://localhost:5001",
"ClientId": "bff_sample",
"ClientSecret": "secret"
},
// ******************** סיום ********************
"Logging": {
"LogLevel": {
"Default": "Information",
ברשום בקצרה את כל ההגדרות ומטרתן:
חלק האובייקטיבציה
אזור: הממשק שלDefaultScheme
מגדיר את השימוש באוטENTIFICATION באופן ברת שיטה בעזרת השיטהCookies
, וDefaultChallengeScheme
מפקיד את הביצוע של האובייקציה לאובייקציה עם השיטהOpenIdConnect
כאשר המשתמש לא יכול להיות אובייקטיבי בעזרת השיטה הברת הסטטוס הבסיסי. אז, כשהמשתמש זר לאפליקציית, יהיה בקשר לשרת אופניד קוננקט עבור אובייקציה, ואחרי כך, המשתמש האובייקטיבי יקבל עקבים אובייקציה, וכל בקשות השרת הבאות יהיו מאובטחות בעזרתם, מבלי ליצור קשר אל השרת אופניד קוננקט.-
OpenIdConnect :
SignInScheme
וSignOutScheme
מספרים את השיטהCookies
, שתשמש לשמר את המידע של המשתמש אחרי ההתחברות.- הנכס
Authority
מכיל את הכתובת הבסיסית של השרת OpenID Connect.ClientId
וClientSecret
מציגים את המזהה והמפתח הסודי של היישומון הלקוחתי, שנרשמו על השרת OpenID Connect. SaveTokens
מרמז על הצורך לשמר את הטוקנים שמקבלים בתוצאה של ההתחברות משרת OpenID Connect.Scope
מכיל רשימה של הסקופים שהיישומוןBffClient
מבקש את הגישה אליהם. במקרה זה, הסקופים הסטנדרטייםopenid
(מידע משתמש),profile
(פרופיל משתמש) וemail
(אימייל) מבקשים.MapInboundClaims
אחראי לשינוי של הטעמים הנכנסים משרת OpenID Connect לטעמים שמשמשים ביישומון. ערך שלfalse
אומר שטעמים יישמרו בהפגנת המשתמש המאומנת בצורה בה הם מקבלים משרת OpenID Connect.ResponseType
עם ערךcode
מציג שהלקוח ישתמש בזרם התהליךAuthorization Code Flow
.ResponseMode
מציג את השדה של הקוד המאמץ בשורה השאלות, שזו השיטה המקבילה בזרם התהליךAuthorization Code Flow
.- הנכס
UsePkce
מרמז על הצורך להשתמש בPKCE בזמן ההתחברות כדי למנוע תפיסת הקוד המאמץ.
GetClaimsFromUserInfoEndpoint
מרמז על הצורך להשיג את המידע של הפרופיל המ
בגלל שהיישומון שלנו אינו מנבא על מגע עם המשתמש בלי הסעת המסגרת, אנחנו נובע לוודא שה-React SPA ייראש רק אחרי ההסעת המסגרת המוצלחת. ברור, אם ה-SPA ייראש ממקור חיצוני כמו מאגר אינטרנט סטטי או מסמך מוצע, לדוגמא, משרתים CDN או משרת הפיתוח המקומי שהתחיל עם הפקת npm start
(לדוגמא, כשמנבצע את הדוגמא שלנו במצב בדיקה). במקרה זה, לא נוכל לבדוק את מצב ההסעת המסגרת לפני הראשית של ה-SPA. אך כשהרקע המקומי .NET אחראי להטעת ה-SPA, זה אפשרי.
כדי לעשות את זה, תוסף את המידבק האחראי לאימוץ והסימנים בקובץ BffSample\Program.cs
:
app.UseRouting();
// ******************* התחלה *******************
app.UseAuthentication();
app.UseAuthorization();
// ******************** סיום ********************
בסוף הקובץ BffSample\Program.cs
, במקום בו מעברים לטעת ה-SPA, תשנה את הדרישה לאימוץ, .RequireAuthorization()
:
app.MapFallbackToFile("index.html").RequireAuthorization();
בדיקת המשתמש בשרת הOpenID Connect.
כפי שהזכרתי קודם, עבור דוגמא האימוץ המעשית, אנחנו נהשתמש בשרת בדיקה לOpenID Connect בסיס על תוך ספריית הAbblix OIDC Server. הטבלת הבסיסית ליישום בעזרת ASP.NET Core MVC עם ספריית Abblix OIDC Server
גם זמינה בעזרת הערך Abblix.Templates
שהושתמשנו קודם. בואו נהשתמש בטבלת זו כדי ליצור יישום חדש בשם OpenIDProviderApp
:
dotnet new abblix-oidc-server -n OpenIDProviderApp
כדי להגדיר את השרת, אנחנו צריכים לרשמה את היישום BffClient
כלוע בשרת OpenID Connect ולהוסיף משתמש בדיקה. כדי לעשות את זה, בואו נוסף את הבלוקים הבאים לקובץ OpenIDProviderApp\Program.cs
:
var userInfoStorage = new TestUserStorage(
// ******************* START *******************
new UserInfo(
Subject: "1234567890",
Name: "John Doe",
Email: "[email protected]",
Password: "Jd!2024$3cur3")
// ******************** END ********************
);
builder.Services.AddSingleton(userInfoStorage);
// ...
// רישום והגדרה של Abblix OIDC Server
builder.Services.AddOidcServices(options =>
{
// הגדרות של שרת OIDC כאן:
// ******************* START *******************
options.Clients = new[] {
new ClientInfo("bff_sample") {
ClientSecrets = new[] {
new ClientSecret {
Sha512Hash = SHA512.HashData(Encoding.ASCII.GetBytes("secret")),
}
},
TokenEndpointAuthMethod = ClientAuthenticationMethods.ClientSecretPost,
AllowedGrantTypes = new[] { GrantTypes.AuthorizationCode },
ClientType = ClientType.Confidential,
OfflineAccessAllowed = true,
PkceRequired = true,
RedirectUris = new[] { new Uri("https://localhost:5003/signin-oidc", UriKind.Absolute) },
PostLogoutRedirectUris = new[] { new Uri("https://localhost:5003/signout-callback-oidc", UriKind.Absolute) },
}
};
// ******************** END ********************
// הכתובת הבאה מובילה לפעולת הכניסה של הAuthController
options.LoginUri = new Uri($"/Auth/Login", UriKind.Relative);
// השורה הבאה מייצרת מפתח חדש עבור חתיכת התווים. תחליףו אותה אם ברצונכם להשתמש במפתחים משלכם.
options.SigningKeys = new[] { JsonWebKeyFactory.CreateRsa(JsonWebKeyUseNames.Sig) };
});
בואו נבדוק את הקוד הזה בפרט. אנחנו רושמים לקlient עם המזהה bff_sample
והמפתח הסודי secret
(שמאחסן בתור האשכל של SHA512), מציעים שההגעה לטוקן תשמש את אמונה הלקlient עם המפתח הסודי שנשלח במסר הPOST (ClientAuthenticationMethods.ClientSecretPost
). AllowedGrantTypes
סופק שהקlient רשום רק להשתמש בזרם האישור. ClientType
מגדיר את הקlient כבטחה, שאומר שהוא יכול לאחסן בטחון את מפתחו הסודי. OfflineAccessAllowed
מאפשר לקlient להשתמש בטוקנים הרעננים. PkceRequired
דורש את השימוש בPKCE בזמן ההתחברות. RedirectUris
ו PostLogoutRedirectUris
מכילים רשימות של כתובות URL מורשות להזיזה אחרי ההתחברות והסיום ההפעלה, בדיוק.
עבור שרת קישורים OpenID Connect אחר, ההגדרות יהיו דומות, עם ההבדלים רק באופן בו הן מוגדרות.
אמצעיית ה BFF API הבסיסי
לפני כן, הזכרנו שבשימוש בערך Microsoft.AspNetCore.Authentication.OpenIdConnect
באופן מוטל באופן מקביל על הצגת האמונה החוצה אחת לאפליקציית הדגימה שלנו. עכשיו, הגיע הזמן לבנות את חלק האחר של ה API BFF. אנחנו נשתמש בשליטה MVC ASP.NET עבור הפינות הנוספים האלה. בואו נתחיל בהוספת תיבת Controllers
וקובץ BffController.cs
בפרוייקט BffSample
עם הקוד הבא בתוכו:
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Mvc;
namespace BffSample.Controllers;
[ApiController]
[Route("[controller]")]
public class BffController : Controller
{
public const string CorsPolicyName = "Bff";
[HttpGet("check_session")]
[EnableCors(CorsPolicyName)]
public ActionResult> CheckSession()
{
// שיחזור 401 לא מורשה על מנת לדחוף את SPA להיווצר הסיום הכניסה
if (User.Identity?.IsAuthenticated != true)
return Unauthorized();
return User.Claims.ToDictionary(claim => claim.Type, claim => claim.Value);
}
[HttpGet("login")]
public ActionResult> Login()
{
// המבנה המדעי להתחיל את הזרם של הקוד הרשמה
return Challenge(new AuthenticationProperties { RedirectUri = Url.Content("~/") });
}
[HttpPost("logout")]
public IActionResult Logout()
{
// המבנה המדעי לעיתים לעזוב את המשתמש
return SignOut();
}
}
בואו נפרס את הקוד של המוסך הזה בפירוט:
- התג המתאים
[Route("[controller]")]
מעצב את המסלול הבסיסי לכל פעולות במוסך. במקרה זה, המסלול יתאים לשמו של המוסך, אז כל הפתרונות לשימוש ב API שלנו יתחילו ב/bff/
. - המשתיית
CorsPolicyName = "Bff"
מגדירה את שמו של מדינית השיתוף המקומית (CORS – Cross-Origin Resource Sharing) שתשמש בתגיות הפעולות. אנחנו יהיה מתיחסים לה בזמן מאוחר יותר. - שלושת הפעולות
CheckSession
,Login
, וLogout
מבצעות את הפעולות הנחוצות ל BFF הנתונים הקטנים האלה. הן מטפלות בבקשות GET ב/bff/check_session
,/bff/login
ובקשות POST ב/bff/logout
באופן ספציפי. - הפעולה
CheckSession
בדוקה את מצב האימות של המשתמש. אם המשתמש אינו מאומן, היא מחזירה קוד 401 לא מורשה, שאמור לדחוף את SPA להיווצר הסיום הכניסה. אם האימות מצליחה, היא מחזירה ערך של ציונים והערכים שלהם. הפעולה הזו כוללת גם קשר למדינית CORS בשםCorsPolicyName
בגלל שהבקשה לפעולה הזו עשויה להיות מקומית ולהכיל קוקים המשמשים לאימות המשתמש. - השימוש בשיטת הכניסה הזו נעצר על ידי הSPA אם השיחזור לפעולה
CheckSession
הם קיבלו401 אינטגרציית לא מורשת
. הוא מוודא שהמשתמש עדיין לא מוכרע ומתחיל את תהליך הChallenge
המוגדר, שיוביל להזיזה אל שרת הOpenID Connect, לאימוץ משתמש בעזרת זרימת הAuthorization Code Flow
ו-PKCE, ולייצור קוקי מסוגנות אמינות. אחרי כך, השלט יחזור לשורה הראשונה של היישומון שלנו"~/"
, שיגרום לSPA לרענן ולהתחיל עם משתמש מוכרע. - השימוש בשיטת ההתנתקות גם נעצר על ידי הSPA אך מסיימת ההתנתקות הנוכחית. הוא מסיר את קוקיים האמינות המיוצרים על ידי חלק השרת של
BffSample
וגם מתקרב אל נקודת הסיום החיצונית על צד של שרת OpenID Connect.
הגדרת CORS עבור BFF
כפי שהזכרתי למעלה, השיחזור לפעולה CheckSession
מועד לשימוש בקולטים אסינכרוניים מהSPA (בדרך כלל בעזרת הAPI Fetch). התפקדות המקצועית של הפעולה הזו תלויה ביכולת לשלוח קוקיים האמינות מהדפדפן. אם הSPA מוטעה מקורבים של מאגר אינטרנט סטטי, כמו סרבר אוטומטי או CDN, או שרת פיתוח פעיל על פורט נפרד, השיחזור נהיה לא באותו מחשבה. זה גורם לדחיית המדינה הזאת הנחוצה, בלי היא הSPA להיות מסוגל לקחת בשימוש בפעולה זו.
אנחנו כבר מצביעים בקוד הבקר בקובץ Controllers
// ******************* התחלה *******************
using BffSample.Controllers;
// ******************** סוף ********************
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
// ...
builder.Services
.AddAuthorization()
.AddAuthentication(options => configuration.Bind("Authentication", options))
.AddCookie()
.AddOpenIdConnect(options => configuration.Bind("OpenIdConnect", options));
// ******************* התחלה *******************
builder.Services.AddCors(
options => options.AddPolicy(
BffController.CorsPolicyName,
policyBuilder =>
{
var allowedOrigins = configuration.GetSection("CorsSettings:AllowedOrigins").Get();
if (allowedOrigins is { Length: > 0 })
policyBuilder.WithOrigins(allowedOrigins);
policyBuilder
.WithMethods(HttpMethods.Get)
.AllowCredentials();
}));
// ******************** סוף ********************
var app = builder.Build();
הקוד מאפשר למדידות הפילוג CORS להיות מוכרחות מקורות מסוימים שסוגרים ברשימת מילים בעיקרון CorsSettings:AllowedOrigins
, באמצעות שימוש בשיטה GET
ומאפשר לעבודה עם עוגיס בקשת זו. בנוסף, הוא דורש לוודא שההתקשרות לapp.UseCors(...)
מומצעת ממש לפני app.UseAuthentication()
:
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
// ******************* התחלה *******************
app.UseCors(BffController.CorsPolicyName);
// ******************** סוף ********************
app.UseAuthentication();
app.UseAuthorization();
כדי לוודא שהמדידה CORS פועלת נכונה, הוסף את ההגדרה הנדרשת לתוך הקונFIG העוצמתי BffSample\appsettings.Development.json
:
{
// ******************* התחלה *******************
"CorsSettings": {
"AllowedOrigins": [ "https://localhost:3000" ]
},
// ******************** סוף ********************
"OpenIdConnect": {
"Authority": "https://localhost:5001",
"ClientId": "bff_sample",
במודל שלנו, הכתובת https://localhost:3000
היא המקור בו מדרגת השירות עם ה SPA React בעזרת הפקודה npm run dev
. ניתן למצוא את הכתובת הזו במקרה שלך על-ידי פתיחת הקובץ BffSample.csproj
ומציאת ערך של הפרמטר SpaProxyServerUrl
. ביישום אמיתי, מדידת הפילוג CORS עשויה לכלול את כתובת ה CDN (Content Delivery Network) שלך או שירות דומה. חשוב לזכור שאם ה SPA שלך מועבר מכתובת אחרת ופרט מהשירות המספק את ה BFF API, עליך להוסיף את הכתובת הזו לתצורת המדידה CORS.
ביצוע האמת מוחשית דרך BFF באפליקציית React
אנחנו ביצענו את ה API BFF בצד השרת. עכשיו הגיע הזמן להתמקד באפליקציית React SPA ולהוסיף את הפונקציות הנדרשות כדי לקרוא ל API הזה. בואו נתחיל בעבורה לתוך תיבת BffSample\ClientApp\src\
, בנינו תיבה components
, ונוסיף קובץ Bff.tsx
עם התוכן הבא:
import React, { createContext, useContext, useEffect, useState, ReactNode, FC } from 'react';
// הגדרה של ההקשר של BFF
interface BffContextProps {
user: any;
fetchBff: (endpoint: string, options?: RequestInit) => Promise;
checkSession: () => Promise;
login: () => void;
logout: () => Promise;
}
// יצירת ההקשר של BFF כדי לשתף מצבים ופונקציות ברחבי היישום
const BffContext = createContext({
user: null,
fetchBff: async () => new Response(),
checkSession: async () => {},
login: () => {},
logout: async () => {}
});
interface BffProviderProps {
baseUrl: string;
children: ReactNode;
}
export const BffProvider: FC = ({ baseUrl, children }) => {
const [user, setUser] = useState(null);
// הגדרת הכתובת הבסיסית באופן נורמלי על ידי הסרת הסלוש הסופי כדי למנוע כתובות URL אינקורסיביות
if (baseUrl.endsWith('/')) {
baseUrl = baseUrl.slice(0, -1);
}
const fetchBff = async (endpoint: string, options: RequestInit = {}): Promise => {
try {
// הפונקציית ההוצאה כוללת מידע מסויים על מנת לטפל בעוגיות, שהם נחוצים לאימות
return await fetch(`${baseUrl}/${endpoint}`, {
credentials: 'include',
...options
});
} catch (error) {
console.error(`Error during ${endpoint} call:`, error);
throw error;
}
};
// הפונקציית ההתחברות מונחתת לדף ההתחברות כשהמשתמש צריך להתחבר
const login = (): void => {
window.location.replace(`${baseUrl}/login`);
};
// הפונקציית הבדיקה היא אחראית לבדיקת ההתחברות של המשתמש בעת הריסוף הראשוני
const checkSession = async (): Promise => {
const response = await fetchBff('check_session');
if (response.ok) {
// אם ההתחברות חוקית, עדכון המצב של המשתמש עם המידע המקבל מהטעם
setUser(await response.json());
} else if (response.status === 401) {
// אם המשתמש אינו מתחבר, מונחת אותו לדף ההתחברות
login();
} else {
console.error('Unexpected response from checking session:', response);
}
};
// פונקציית ההתנתקות של המשתמש
const logout = async (): Promise => {
const response = await fetchBff('logout', { method: 'POST' });
if (response.ok) {
// מיקום חדש לדף הבית אחרי ההתנתקות המוצלחת
window.location.replace('/');
} else {
console.error('Logout failed:', response);
}
};
// useEffect משמש כדי לבצע את הבדיקה של ההתחברות ברגע שהמרכיב מוטעה
// זה מובטח שההתחברות נבדוקת בהתחלה של היישום
useEffect(() => { checkSession(); }, []);
return (
// ספקת להקשר של BFF עם ערכים ופונקציות הניתנים לשימוש ברחבי היישום
{children}
);
};
// הקבילה המותאמת את ההקשר של BFF בקלות במרכיבים אחרים
export const useBff = (): BffContextProps => useContext(BffContext);
// ייצא את הHOC כדי לספק גישה להקשר BFF
export const withBff = (Component: React.ComponentType) => (props: any) =>
{context => }
;
קבעת הקובץ מייצאת:
- המרכיב
BffProvider
המייצג את ההקשר של BFF - המסגרת המותאמת המותאמת לשימוש ב-
useBff()
המחזירה אובייקט של מצב המשתמש הנוכחי ופונקציות לעבודה עם BFF:checkSession
,login
, וlogout
. היא מיועדת לשימוש ברכיבי React פונקציונליים. - הרכב הרמה-על (HOC)
withBff
לשימוש ברכיבי React מבוססים על קוlass.
בהמשך, בונה רכיב UserClaims
, שיראה את הטעמים של המשתמש הנוכחי אחרי ההתחברות המוצלחת. יצירה קובץ UserClaims.tsx
בתוך התיקייה BffSample\ClientApp\src\components
עם התוכן הבא:
import React from 'react';
import { useBff } from './Bff';
export const UserClaims: React.FC = () => {
const { user } = useBff();
if (!user)
return <div>Checking user session...</div>;
return (
<>
<h2>User Claims</h2>
{Object.entries(user).map(([claim, value]) => (
<div key={claim}>
<strong>{claim}</strong>: {String(value)}
</div>
))}
</>
);
};
הקוד הזה מבחינה במשתמש מאומן בעזרת הקובץ useBff()
ומציג את טעמי המשתמש בתוצרת רשימה אם המשתמש מאומן. אם המידע של המשתמש עדיין אינו זמין, הוא מציג את הטקסט Checking user session...
.
עכשיו, תעבורו לקובץ BffSample\ClientApp\src\App.tsx
. שיחזור את התוכן המסויים עליו. יביא את BffProvider
מ-components/Bff.tsx
ו-UserClaims
מ-components/UserClaims.tsx
, והכניסה את הקוד העיקרי:
import { BffProvider, useBff } from './components/Bff';
import { UserClaims } from './components/UserClaims';
const LogoutButton: React.FC = () => {
const { logout } = useBff();
return (
<button className="logout-button" onClick={logout}>
Logout
</button>
);
};
const App: React.FC = () => (
<BffProvider baseUrl="https://localhost:5003/bff">
<div className="card">
<UserClaims/>
</div>
<div className="card">
<LogoutButton />
</div>
</BffProvider>
);
export default App;
כאן, המפתן baseUrl
מציג את הכיוון הבסיסי של API BFF שלנו https://localhost:5003/bff
. השיפור הזה מעוצב בכדי פשטות והוא מיועד לסיפור הקצר. בייצור אמיתי, צריך לספק את ההגדרה הזו דינמית במקום שהיא מוקבעת בקוד. יש דרכים מסויימות להגיע לזה, אך הדיבור עליהן מעבר לתוך תוך המאמר הזה.
הכפתור התנתקות
מאפשר למשתמש להתנתק. הוא מעלה את הפונקציית logout
שזמינה דרך הקוק useBff
ומסייע בהפניית הדפדפן של המשתמש אל נקודת הקישור /bff/logout
, שמסיימת את ההפעלה של ההפעלה של המשתמש מצד השרת.
בשלב זה, ניתן להריץ עכשיו את היישום BffSample
ביחד עם OpenIDProviderApp
ולבדוק את הפונקציות שלו. ניתן להשתמש בפקודה dotnet run -lp https
בכל פרוייקט אחד או בIDE המועדף עליך כדי להתחיל אותם. שני היישומים חייבים להיות פעלים בו-זמנית.
אחרי זה, פתח את הדפדפן שלך ועבורן את https://localhost:5003
. אם הכל הוגדר נכון, הSPA יתעלם ויבקש /bff/check_session
. נקודת הקישור /check_session
תשחזר תגובה 401, שתעודד את הSPA להפנות את הדפדפן אל /bff/login
, שייתחיל את ההסתברות המשנהה על השרת דרך הזרם הראשון של OpenID Connect עם PKCE. ניתן להסתכל על סדר הבקשות הזה על ידי פתיחת הלוח הפיתוח בדפדפן שלך ועל ידי עבירה לשורה הנטוורק. אחרי ההגעה המוצלחת לפרטי המשתמש ([email protected]
, Jd!2024$3cur3
), השלט יחזר לSPA, ותראה את טענות המשתמש המאובטח בדפדפן:
sub: 1234567890
sid: V14fb1VQbAFG6JXTYQp3D3Vpa8klMLcK34RpfOvRyxQ
auth_time: 1717852776
name: John Doe
email: [email protected]
בנוסף, לחיצה על הכפתור התנתקות
תסייע בהפניית הדפדפן אל /bff/logout
, שיסיים את ההתנתקות ותראה שוב את דף ההתחברות עם בק
אם תופגעו בשגיאות, אתם יכולים להשוות את הקוד שלכם עם האחסון GitHub שלנו Abblix/Oidc.Server.GettingStarted, שמכיל את הדוגמאות האלה ואחרות מוכנות להריץ.
פתרון בעיות אמון סימנים HTTPS
כשאתם מבצעים בדיקות מקומיות באפליקציות אינטרנט שמורכבות על הטעם HTTPS, יתפגעו אתם על-ידי האזהרות של הדפדפן שהסמל הSSL אינו מואמן. הבעיה הזאת נובעת בגלל שסמלים הפיתוח המשמשים על-ידי ASP.NET Core אינם ניתנים על-ידי סמכת המועצה המובילה (CA) אלא מוכתבים עצמאית או אינם נמצאים במערכת בכלל. האזהרות האלה יכולות להימנע על-ידי ביצוע הפקודה הבאה פעם אחת:
dotnet dev-certs https --trust
הפקודה הזו מייצרת סמל עצמאי עבור localhost
ומתקנת אותו במערכת שלכם כך שהסמל מואמן. הסמל ישמש בעזרת ASP.NET Core להריץ את האפליקציות האינטרנט במקום. אחרי שיבוץ הפקודה הזו, הפעם מתחדש את הדפדף שלכם כדי שהשינויים ירוקמו.
הערה מיוחדת עבור משתמשי Chrome: אפילו אחרי התקנת הסמל הפיתוח כמונים, גירסאות מסויימות של Chrome עדיין עשויות למנוע גישה לאתרי localhost
בשל סיפורי בטיחות. אם תופגעו בשגיאה שמציעה שהחיבור אינו בטוח והגישה לlocalhost
מועצמת על-ידי Chrome, אתם יכולים למעשה את זה באופן הבא:
- לחץ על כל מקום בדף השגיא
חשוב להשתמש בשיטות עוקפות אלו רק בתרחישי פיתוח, מכיוון שהן עלולות להוות סיכון אבטחה ממשי.
קריאה ל-API של צד שלישי באמצעות BFF
הטמענו בהצלחה אימות באפליקציית BffSample
שלנו. עכשיו נמשיך לקריאה ל-API של צד שלישי שדורש אסימון גישה.
דמיינו שיש לנו שירות נפרד המספק את הנתונים הדרושים, כמו תחזיות מזג אוויר, והגישה אליו ניתנת רק עם אסימון גישה. תפקידו של החלק השרת ב-BffSample
יהיה לשמש כפרוקסי הפוך, כלומר, לקבל ולאמת את הבקשה לנתונים מה-SPA, להוסיף אליה את אסימון הגישה, להעביר את הבקשה לשירות תחזיות מזג האוויר, ואז להחזיר את התשובה מהשירות בחזרה ל-SPA.
יצירת שירות ApiSample
לפני שנדגים את קריאת ה-API המרוחקת דרך ה-BFF, עלינו ליצור אפליקציה שתשמש כ-API בדוגמה שלנו.
כדי ליצור את האפליקציה, נשתמש בתבנית שסופקה על ידי .NET. נווטו לתיקייה המכילה את פרויקטי OpenIDProviderApp
ו-BffSample
, והפעילו את הפקודה הבאה כדי ליצור את אפליקציית ApiSample
:
dotnet new webapi -n ApiSample
אפליקציית ה-ASP.NET Core Minimal API זו משרתת נקודת קצה יחידה עם נתיב /weatherforecast
שמספקת נתוני מזג אוויר בפורמט JSON.
ראשית, שינוי מספר פורט המיועד באופן אקראי שהיישומון ApiSample
משתמש בו למשך השימוש המקומי לפורט קבוע, 5004. כפי שהזכרתי קודם, שלב זה אינו מובעד, אך הוא משפר את ההגדרה שלנו. על מנת לבצע את השינוי הזה, פתחו את הקובץ ApiSample\Properties\launchSettings.json
, מוצאו את הפרופיל שנקרא https
ושינויו בשפע של הנתונים של applicationUrl
ל https://localhost:5004
.
עכשיו בואו נגיש את ה API של המזג אוויר רק עם תוקף גישה. ניוגד לתוך תיבת הפרוייקט ApiSample
ונוסף עותק של החבילה NuGet לאמצעי אימות JWT Bearer token:
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
מיועדת את השיטה לאמון ומדיניות האימון בשם WeatherApi
בקובץ ApiSample\Program.cs
:
// ******************* התחלה *******************
using System.Security.Claims;
// ******************** סיום ********************
var builder = WebApplication.CreateBuilder(args);
// הוספת שירותים למאגר.
// למידע נוסף על הגדרת Swagger/OpenAPI ב https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
// ******************* התחלה *******************
var configuration = builder.Configuration;
builder.Services
.AddAuthentication()
.AddJwtBearer(options => configuration.Bind("JwtBearerAuthentication", options));
const string policyName = "WeatherApi";
builder.Services.AddAuthorization(
options => options.AddPolicy(policyName, policy =>
{
policy.RequireAuthenticatedUser();
policy.RequireAssertion(context =>
{
var scopeValue = context.User.FindFirstValue("scope");
if (string.IsNullOrEmpty(scopeValue))
return false;
var scope = scopeValue.Split(' ', StringSplitOptions.RemoveEmptyEntries);
return scope.Contains("weather", StringComparer.Ordinal);
});
}));
// ******************** סיום ********************
var app = builder.Build();
קטע הקוד הזה מוגדר את האמון על ידי קריאה לעיצוב מהיר מההגדרות היישומיות, כולל אימון בעזרת JWT (תוכניות JSON Web Tokens) ומתואמת את מדיניות האימון בשם WeatherApi
. מדיניות האימון WeatherApi
מגדירה את הדרישות הבאות:
policy.RequireAuthenticatedUser()
: מובטחת שרק משתמשים מוכרתים יוכלו לגשת למשאבים מוגנים.מדינת ההגיון
: המשתמש צריך להיות בעלטעם
שכולל את הערךמזג אוויר
. מפני שהטעםמזג אוויר
יכול לכלל מספר ערכים מפרדים על-ידי חלקים בהתאם לRFC 8693, הערך הממוצע שלמזג אוויר
נפרד לחלקים בודדים, ואז נבדוק אם המערך הזה כולל את הערך הנדרשמזג אוויר
.
ביחד, התנאים אלה מובנים שרק משתמשים מאומנים עם טוקן גישה שמורש עבור המזג אוויר
יכולים לקרוא את הקצה המגינה על המדינת.
אנחנו צריכים ליישם את המדינת הזו על הקצה /weatherforecast
. הוסף את הקריאה לדרוש סיסמה
כפי שרואה באיור הבא:
app.MapGet("/weatherforecast", () =>
{
// ...
})
.WithName("GetWeatherForecast")
// ******************* התחלה *******************
.WithOpenApi()
.RequireAuthorization(policyName);
// ******************** סיום ********************
הוסף את ההגדרות הנחוצות לאסטרטגיית ההסמכה לתוך הקובץ appsettings.Development.json
של היישומה ApiSample
:
{
// ******************* התחלה *******************
"JwtBearerAuthentication": {
"Authority": "https://localhost:5001",
"MapInboundClaims": false,
"TokenValidationParameters": {
"ValidTypes": [ "at+jwt" ],
"ValidAudience": "https://localhost:5004",
"ValidIssuer": "https://localhost:5001"
}
},
// ******************** סיום ********************
"Logging": {
"LogLevel": {
"Default": "Information",
בואו נבדוק את כל ההגדרות בפרט:
סמכות
: זוהי הכתובת המייצגת את השרת הסיסמה הפתוח שמייצר טוקני JWT. ספקיית ההסמכה הנתנת ביישומהApiSample
תהיה משתמשת בכתובת הזו כדי לקבל את המידע הנחוץ עבור האימות של טוקנים, כמו מפתחות חתיכה.מפעילת טענות הכניסה
: הגדרה זו שולטת איך טענות מגיעות מהטוקן JWT מותאמות לטענות פנימיות בASP.NET Core. היא מוגדרת כשגרה
, משמע שטענות ישמשו את שמותיהם המקוריים מהטוקן JWT.פרמטרים הולידציה טוקנים
:סוגי הטוקנים החוקיים
: מוגדרים כat+jwt
, שלפי RFC 9068 2.1 זה אומר טוקן גישה בתוצרת JWT.
קהל חוקי
: מספק שהיישום יקבל טוקנים שיוצרים עבור הלקוחhttps://localhost:5004
.
יוצא חוקי
: מספק שהיישום יקבל טוקנים שיוצרים על-ידי השרתhttps://localhost:5001
.
הגדרות נוספות של ספק הOpenIDProviderApp
שילוב שירות האימות OpenIDProviderApp
והיישום הלקוח BffSample
עובד טוב בשביל ספק אימות למשתמשים. עם זאת, כדי לאפשר את הקריאות ל API מרוחק, אנחנו צריכים לרשום את היישום ApiSample
בתור משאב בשירות OpenIDProviderApp
. במודל שלנו, אנחנו משתמשים ב Abblix OIDC Server
, שתומך ב RFC 8707: Resource Indicators for OAuth 2.0. לכן, אנחנו נרשמה את היישום ApiSample
בתור משאב עם הסיקוף weather
. אם אתה משתמש בשרת OpenID Connect אחר שאינו תומך בסימנים משאב, עדיין מומלץ לרשום סיקוף ייחודי להתמודדות עם ה API המרוחק הזה (כמו weather
במודל שלנו).
הוסף את הקוד הבא לקובץ OpenIDProviderApp\Program.cs
:
// רשום וסוגב שרת Abblix OIDC
builder.Services.AddOidcServices(options => {
// ******************* התחלה *******************
options.Resources =
[
new(new Uri("https://localhost:5004", UriKind.Absolute), new ScopeDefinition("weather")),
];
// ******************* סיום *******************
options.Clients = new[] {
new ClientInfo("bff_sample") {
בתרגום זה, אנחנו רושמים את האפליקציה ApiSample
, ומציינים את כתובת הבסיס שלה https://localhost:5004
כמשאב ומגד תחום ספציפי בשם weather
. באפליקציות האמיתי, במיוחד עם ממשקי API מורכבים הכוללים נקודות קצה רבות, מומלץ להגדיר תחומים נפרדים לכל נקודת קצה או קבוצת נקודות קצה קשורות. ג זו מאפשרת שליטה מדויקת יותר בגישה ומספקת גמישות בניהול זכויות גישה. לדוגמה, תוכלו ליצור תחומים נפרדים לפעולות שונות, מודולי אפליקציה, או רמות גישה של משתמשים, מה שמאפשר שליטה מפורטת יותר על מי יכול לגשת לחלקים ספציפיים של ה-API שלכם.
הרחבה של BffSample לפרוקסי בקשות ל- מרוחק
אפליקציית הלקוח BffSample
צריכה כעת לעשות יותר מאשר רק לבקש אסימון גישה עבור ApiSample
. היא צריכה גם לטפל בבקשות מה-SPA-API המרוחק. זה כולל הוספת האסימון שנלקח משירות OpenIDProviderApp
לבקשות אלו, העברתם לשרת המרוחק, ולאחר מכן החזרת התגובות של השרת ל-SPA. למעשה, BffSample
צריכה לתפקד כשרת פרוקסי הפוך.
במקום ליישם פרוקסי בק באופן ידני באפליקציית הלקוח שלנו, נשתמש בARP (Yet Another Reverse Proxy), מוצר מוכן שפותח על ידי מיקרוס. YARP הוא שרת פרוקסי הפוך שנכתב ב-.NET וזמין כחבילת NuGet.
כדי להשתמש ב-YARP באפליקציית BffSample
, ראשית הוסף את חבילת ה-NuGet:
dotnet add package Yarp.ReverseProxy
לאחר מכן הוסף את מרחבי השמות הבאים בתחילת הקובץ <>Bff\Program.cs:
using Microsoft.AspNetCore.Authentication;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using System.Net.Http.Headers;
using Yarp.ReverseProxy.Transforms;
לפני הקול הvar app = builder.Build();
, הוסף את הקוד:
builder.Services.AddHttpForwarder();
ובין הקולים לapp.MapControllerRoute()
וapp.MapFallbackToFile()
:
app.MapForwarder(
"/bff/{**catch-all}",
configuration.GetValue("OpenIdConnect:Resource") ?? throw new InvalidOperationException("Unable to get OpenIdConnect:Resource from current configuration"),
builderContext =>
{
// מסיר את הפקסים "/bff" מהמסלול הבקשה
builderContext.AddPathRemovePrefix("/bff");
builderContext.AddRequestTransform(async transformContext =>
{
// משיג את האקס טוקן המקבל קודם בתהליך האימות
var accessToken = await transformContext.HttpContext.GetTokenAsync(OpenIdConnectParameterNames.AccessToken);
// מוסף ראש עם האקס טוקן לבקשת הפרוקסי
transformContext.ProxyRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
});
}).RequireAuthorization();
בואו נפרס מהאופן בו הקוד הזה פועל:
builder.Services.AddHttpForwarder()
רשם את השרותים הנחוצים לYARP במאגר הDI.app.MapForwarder
מכין את ההעברה של הבקשות לשרת אחר או לנקודת הקישור."/bff/{**catch-all}"
התבנית המסלול לבקשות שהמעביר הפועל יגייס להגנה. כל בקשות התחילות עם/bff/
ייעברו על ידי YARP.{**catch-all}
משמש לתפוס את כל החלקים הנותרים של הURL אחרי/bff/
.configuration.GetValue<string>("OpenIdConnect:Resource")
משתמש בהגדרות היישומים למשך למשיג את הערך מהחלקOpenIdConnect:Resource
. ערך זה סוגר את הכתובת המשאבים להפנייה אליה. במודל שלנו, הערך יהיהhttps://localhost:5004
– כתובת הבסיס בה היישומוןApiSample
פועל.builderContext => ...
מוסיף את הטרנספורמציות הנדרשות ש-YARP יבצע על כל בקשה נכנסת מה-SPA. במקרה שלנו, יהיו שתי טרנספורמציות כאלו:builderContext.AddPathRemovePrefix("/bff")
מסיר את התחילית/bff
מנתיב הבקשה המקורי.builderContext.AddRequestTransform(async transformContext => ...)
מוסיף כותרת HTTP מסוגAuthorization
לבקשה, המכילה את אסימון הגישה שהושג קודם לכן במהלך האימות. כך, הבקשות מה-SPA ל-API המרוחק יאומתו באמצעות אסימון הגישה, למרות שה-SPA עצמו לא גולש לאסימון זה.
.RequireAuthorization()
מציין כי נדרשת הרשאה לכל הבקשות המועברות. רק משתמשים מורשים יוכלו לגשת לנתיב/bff/{**catch-all}
.
כדי לבקש תוקן גישה עבור המשאב https://localhost:5004
בזמן ההניעה, הוסיף פרמטר Resource
עם הערך https://localhost:5004
לתצורת OpenIdConnect
במפתן BffSample/appsettings.Development.json
:
"OpenIdConnect": {
// ******************* התחלה *******************
"Resource": "https://localhost:5004",
// ******************** סיום ********************
"Authority": "https://localhost:5001",
"ClientId": "bff_sample",
בנוסף, הוסיף עוד ערך weather
לרשימת scope
במפתן BffSample/appsettings.json
:
{
"OpenIdConnect": {
// ...
// ******************* התחלה *******************
"Scope": ["openid", "profile", "email", "weather"],
// ******************** סיום ********************
// ...
}
}
הערכים: בפרוייקט אמיתי, נחוץ לנטר את התמוטטות של התוקן הגישה. כשהתוקן עומד להגיע לסיום התמוטטות, עליך לבקש חדש באופן מקדם בעזרת תוקן השיחזור משרת ההניעה או להתמודד עם שגיאה בדחיית הגישה מ-API הרחוק על ידי ביסוס תוקן חדש ושיחזור בבקשה המקורית. בשביל הקצרה, השימו במחשבה את האסpect הזה במאמר הזה.
ביקש את API המזג אוויר דרך BFF ביישומן SPA
הבחינה האחרונה היא להוסיף את הפונקציה לבקש את הAPI הזה ולהציג את הנתונים שנאסף בתוך הריאקט SPA.
בוסף את הקובץ WeatherForecast.tsx
ב BffSample\ClientApp\src\components
עם התוכן הבא:
import React, { useEffect, useState } from 'react';
import { useBff } from './Bff';
interface Forecast {
date: string;
temperatureC: number;
temperatureF: number;
summary: string;
}
interface State {
forecasts: Forecast[];
loading: boolean;
}
export const WeatherForecast: React.FC = () => {
const { fetchBff } = useBff();
const [state, setState] = useState<State>({ forecasts: [], loading: true });
const { forecasts, loading } = state;
useEffect(() => {
fetchBff('weatherforecast')
.then(response => response.json())
.then(data => setState({ forecasts: data, loading: false }));
}, [fetchBff]);
const contents = loading
? <p><em>Loading...</em></p>
: (
<table className="table table-striped" aria-labelledby="tableLabel">
<thead>
<tr>
<th>Date</th>
<th>Temp. (C)</th>
<th>Temp. (F)</th>
<th>Summary</th>
</tr>
</thead>
<tbody>
{forecasts.map((forecast, index) => (
<tr key={index}>
<td>{forecast.date}</td>
<td align="center">{forecast.temperatureC}</td>
<td align="center">{forecast.temperatureF}</td>
<td>{forecast.summary}</td>
</tr>
))}
</tbody>
</table>
);
return (
<div>
<h2 id="tableLabel">Weather forecast</h2>
<p>This component demonstrates fetching data from the server.</p>
{contents}
</div>
);
};
בואו נפרס את הקוד הזה:
- המבנה
Forecast
מגדיר את מבנה הנתונים של החיזוי המזג אוויר, שכולל את התאריך, הטמפרטורה בקלים ובחמה, וסיכום של המזג אוויר. המבנהState
מתאר את מבנה המצב של הרכבן, שמורכב מרשימה של חיזויי המזג אוויר ודגל בעליבת נתונים. - הרכבן
WeatherForecast
משלך את הפונקצייתfetchBff
מהקשרuseBff
ומשתמש בה כדי לשלוח בעיקרה אחר נתוני המזג אוויר מהשרת. המצב של הרכבן נשלט בעזרת הקשרuseState
, שמתחיל עם רשימה ריקה של חיזויים ודגל בעליבת נתונים שמוגדר כאמתי. - הקשר
useEffect
מעלה את הפונקצייתfetchBff
כשהרכבן מתחיל, שולך בעיקרה אחר נתוני חיזוי המזג אוויר מהשרת אל נקודת המחזיר/bff/weatherforecast
. אחרי שהתגובה מהשרת מקבלת ומומרת לJSON, הנתונים נאחסים במצב הרכבן (דרךsetState
), והדגל בעליבת הנתונים עודכן לאמתיfalse
. - לפי ערך הדגל בעליבת הנתונים, הרכבן מראה את המסר "בהתרמה…" או מוריד טאבלה עם הנתונים של חיזוי המזג אוויר. הטאבלה כוללת עמודים לתאריך, טמפרטורת קלים ובחמה, וסיכום של המזג אוויר לכל חיזוי.
עכשיו, בוסף את הרכבן WeatherForecast
ל <
// ******************* התחלה *******************
import { WeatherForecast } from "./components/WeatherForecast";
// ******************** סוף ********************
// ...
// ******************* התחלה *******************
// ******************** סוף ********************
בעבודה ובבדיקה
אם הכל נעשה נכון, עכשיו אתה יכול להתחיל את שלושת הפרוייקטים שלנו. השתמש בפקודה ה控制台 dotnet run -lp https
עבור כל אפליקציה כדי להריץ אותה עם HTTPS.
אחרי שהם כולם נראים, פתח את היישומה BffSample
בדפדפן שלך (https://localhost:5003) והתחבר בעזרת המידע הבא [email protected]
וJd!2024$3cur3
. אחרי ההתחברות המצלמדת, תראה את רשימת הטעם שקיבלת מהשרת ההרשמה, כפי שראינו קודם. מתחת לזה, תראה גם את החיזור המזגאוני.
החיזור המזגאוני מסופק על ידי היישום נפרד ApiSample
, שמשתמש בטוקן הגישה שיוצא משרת ההרשמה OpenIDProviderApp
. ראיית החיזור המזגאוני בחלון היישום BffSample
מראה שהספא הוצא בהצלחה את הקשר אל הקשר האחורי של BffSample
, אשר אחראי להעביר את הקשר אל ApiSample
על-ידי הוספת הטוקן הגישה. ApiSample
אימוץ את הקשר והגיב עם JSON שמכיל את החיזור המזגאוני.
הפתרון המלא זמין ב GitHub.
אם תפגעו בבעיות או שגיאות במהלך היישום של הפרוייקטים המבחנים, אתם יכולים להתייחס לפתרון המלא שזמין באקסיבייסר גיתאבוס/Oidc.Server.GettingStarted. פשוט תשכל את האקסיבייסר Abblix/Oidc.Server.GettingStarted כדי להשיג את הפרוייקטים המושלמים שאותם המאמר מתאר. המשאב הזה משמש גם ככלי בעייתים וגם כנקודת התחלה מוצקה ליצירת פרוייקטים משלך.
סיכום
האבולוציה של פרOTocolים האותנטיים כמו OAuth 2.0 ו OpenID Connect משקף את המגמות הרחבות באבטחת הרשת וביכולות הדפדפנים. העברת משיטות ישנות כמו הזרם האימפליקטיבי לגישות יותר בטוחות, כמו הזרם הקוד עם PKCE, עשתה את האבטחה באופן משמעותי. עם זאת, הפגיעות הבריאה של הסביבה הבל-שליטתית גורמות להגנה על ספקולציות של פרוייקטים הספרט-פעילים (SPA) להיות משימה קשה. אחסון הטוקנים בעצם רק בחזרה והאקסיבייסר החזרה לקדם (BFF) הוא אסטרטגיה יעילה למנוע סיכונים ולוודא הגנה מעוררת על נתוני המשתמש.
למפתחים חייבים להשאר מאוד מאזינים בטיפול בפני הפנימיות המשתנה של הסיכונים על ידי יישום שיטות האבטחה חדשות וגישות ארכיטקטוריות מעודכנות. הגישה הפעילה הזו היא חיונית לבניית אפליקציות רשת בטוחות ואמינות. במאמר זה, אנחנו חקרנו ויישמנו גישה מודרנית לש
ובזמן שאנחנו מתבוננים לעתיד, ההתפתחות הממשית של אבטחת הרשת תדרש עוד יותר חדשנות באופן האמת האותנטי ובדפוסי הארכיטקטורה. אנחנו מעודדים אתכם לחקור את הארכיון הגיטהבוך שלנו, לתרום לפיתוח של פתרונות אמת ההסמכה המודרניים ולהיות מעורבים בהתקדמויות המתמשכות. תודה רבה לכם על הקצת שלכם!
Source:
https://dzone.com/articles/modern-authentication-on-dotnet