אימות מודרנית ב.NET: OpenID Connect, BFF, SPA

ככל שהטכנולוגיות האינטרנט מתקדמות, כך גם השיטות והפרוטוקולים שנועדו להגן עליהן. פרוטוקולי 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 גם מנבא על נוכחות של אפי בצד של השרת המרכזית של ארבעה קווי קישור עיקריים:

  1. Check Session: משורה על בדיקת סדרה ההתעמת המעורבת של המשתמש. בד "" כ נקראת מעבר ל SPA באמצעות API אינטראקציבי (fetch) ואם בהצלחה, הוא מחזיר מידע על המשתמש המעורב. כך שה SPA, שנוטל ממקור שלישי (לדוגמה, CDN), יכול לבדוק את מצב ההתעמת ולהמשיך את עבודתו עם המשתמש או להמשיך להגיע להתעמת בעזרת השרת OpenID Connect.
  2. כניסה: זה מותח תהליך האמת מזהה בשרת OpenID Connect. בדרך טיפוסית, אם ה SPA אינו מצליח לקבל מידע מובטח של המשתמש בשלב 1 דרך בדיקת ההפעלה, הוא מסייע את הדפדפן לכתובת זו, שאז יוצרת בקשה מלאה לשרת OpenID Connect ומסייעת את הדפדפן לשם.
  3. הכנסה: מקבל את הקוד הסיסמה ששולח השרת לאחר שלב 2 במקרה של האמת המזהה המוצלחת. יוצרת בקשה ישירה אל שרת OpenID Connect כדי להחליף את הקוד הסיסמה זו + מוכח הקוד המוצלח לשימושים וטוקנים חדשים. מותחת הפעלה מובטחה בצד הלקוח על ידי יצירת עוגיית ההתחברות למשתמש.
  4. היציאה: משמש לסגור את ההפעלה המובטחה. בדרך טיפוסית, ה 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, וניתן להתקין אותה על ידי הרצת הפקודה הבאה:

Shell

 

dotnet new install Abblix.Templates

כעת נוכל להשתמש בתבנית בשם abblix-react. בואו נשתמש בה כדי ליצור אפליקציה חדשה בשם BffSample:

Shell

 

dotnet new abblix-react -n BffSample

פקודה זו יוצרת אפליקציה המורכבת מ-backend של .NET WebApi ולקוח React SPA. הקבצים הקשורים ל-SPA ממוקמים בתיקיית BffSample\ClientApp.

לאחר יצירת הפרויקט, המערכת תבקש ממך להריץ פקודה להתקנת התלויות:

Shell

 

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 וביצוע הפקודה הבאה:

Shell

 

dotnet add package Microsoft.AspNetCore.Authentication.OpenIdConnect

ארגן שני תבניות אימות בשם Cookies ו OpenIdConnect בתוך היישום, ואת ההגדרות שלהם מקרינות מההגדרות של היישום. על מנת לעשות את זה, ערכו שינויים בקובץ BffSample\Program.cs:

C#

 

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:

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:

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:

C#

 

app.UseRouting();
// ******************* התחלה *******************
app.UseAuthentication();
app.UseAuthorization();
// ******************** סיום ********************

בסוף הקובץ BffSample\Program.cs, במקום בו מעברים לטעת ה-SPA, תשנה את הדרישה לאימוץ, .RequireAuthorization():

C#

 

app.MapFallbackToFile("index.html").RequireAuthorization();

בדיקת המשתמש בשרת הOpenID Connect.

כפי שהזכרתי קודם, עבור דוגמא האימוץ המעשית, אנחנו נהשתמש בשרת בדיקה לOpenID Connect בסיס על תוך ספריית הAbblix OIDC Server. הטבלת הבסיסית ליישום בעזרת ASP.NET Core MVC עם ספריית Abblix OIDC Server גם זמינה בעזרת הערך Abblix.Templates שהושתמשנו קודם. בואו נהשתמש בטבלת זו כדי ליצור יישום חדש בשם OpenIDProviderApp:

Shell

 

dotnet new abblix-oidc-server -n OpenIDProviderApp

כדי להגדיר את השרת, אנחנו צריכים לרשמה את היישום BffClient כלוע בשרת OpenID Connect ולהוסיף משתמש בדיקה. כדי לעשות את זה, בואו נוסף את הבלוקים הבאים לקובץ OpenIDProviderApp\Program.cs:

C#

 

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 עם הקוד הבא בתוכו:

C#

 

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

C#

 


// ******************* התחלה *******************
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():

C#

 

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
// ******************* התחלה *******************
app.UseCors(BffController.CorsPolicyName);
// ******************** סוף ********************
app.UseAuthentication();
app.UseAuthorization();

כדי לוודא שהמדידה CORS פועלת נכונה, הוסף את ההגדרה הנדרשת לתוך הקונFIG העוצמתי BffSample\appsettings.Development.json:

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 עם התוכן הבא:

TypeScript

 

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 עם התוכן הבא:

TypeScript

 

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, והכניסה את הקוד העיקרי:

TypeScript

 

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, ותראה את טענות המשתמש המאובטח בדפדפן:

Plain Text

 

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) אלא מוכתבים עצמאית או אינם נמצאים במערכת בכלל. האזהרות האלה יכולות להימנע על-ידי ביצוע הפקודה הבאה פעם אחת:

Shell

 

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:

Shell

 

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:

Shell

 

dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer

מיועדת את השיטה לאמון ומדיניות האימון בשם WeatherApi בקובץ ApiSample\Program.cs:

C#

 

// ******************* התחלה *******************
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. הוסף את הקריאה לדרוש סיסמה כפי שרואה באיור הבא:

C#

 

app.MapGet("/weatherforecast", () =>
{

// ...

})
.WithName("GetWeatherForecast")
// ******************* התחלה *******************
.WithOpenApi()
.RequireAuthorization(policyName);
// ******************** סיום ********************

הוסף את ההגדרות הנחוצות לאסטרטגיית ההסמכה לתוך הקובץ appsettings.Development.json של היישומה ApiSample:

JSON

 

{
  // ******************* התחלה *******************
  "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:

C#

 

// רשום וסוגב שרת 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:

Shell

 

dotnet add package Yarp.ReverseProxy

לאחר מכן הוסף את מרחבי השמות הבאים בתחילת הקובץ <>Bff\Program.cs:

C#

 

using Microsoft.AspNetCore.Authentication;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using System.Net.Http.Headers;
using Yarp.ReverseProxy.Transforms;

לפני הקול הvar app = builder.Build();, הוסף את הקוד:

C#

 

builder.Services.AddHttpForwarder();

ובין הקולים לapp.MapControllerRoute() וapp.MapFallbackToFile():

C#

 

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:

JSON

 

  "OpenIdConnect": {
    // ******************* התחלה *******************
    "Resource": "https://localhost:5004",
    // ******************** סיום ********************
    "Authority": "https://localhost:5001",
    "ClientId": "bff_sample",

בנוסף, הוסיף עוד ערך weather לרשימת scope במפתן BffSample/appsettings.json:

JSON

 

{
  "OpenIdConnect": {

    // ...

    // ******************* התחלה *******************
    "Scope": ["openid", "profile", "email", "weather"],
    // ******************** סיום ********************

    // ...

  }
}

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

ביקש את API המזג אוויר דרך BFF ביישומן SPA

הבחינה האחרונה היא להוסיף את הפונקציה לבקש את הAPI הזה ולהציג את הנתונים שנאסף בתוך הריאקט SPA.

בוסף את הקובץ WeatherForecast.tsx ב BffSample\ClientApp\src\components עם התוכן הבא:

TypeScript

 

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 ל <

TypeScript

 

// ******************* התחלה *******************
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