חיפוש
  • יניב אור

מערכת מבוססת microservices - חלק 1 - אימות משתמשים

עודכן ב: מרץ 23

כתבתי שרת Auth (קונטיינר נפרד, כמובן) שיאפשר רישום וכניסה למערכת (Authentication) כמשתמשים מסוגים שונים (Roles) וביצוע פעולות במערכת לפי הרשאות (Authorization).


שרת ה-auth הוא שרת Node - express שיצרתי בעזרת הפקודה:

$ npx express-generator node-auth


ה-repo נמצא כאן: https://github.com/YanivOr/node-auth


כניסה למערכת

בעת בקשת כניסה למערכת (login), נשלחים לשרת אימייל וסיסמא ובמידה והפרטים נכונים - השרת שולח כתגובה קישור URL למערכת המתאימה אליה הדפדפן יעשה העברה. ב-URL נמצא JWT שישמר בדפדפן (localStorage) בהתאם למערכת אליה יועבר המשתמש. מהשלב הזה ואילך, יועבר ה-JWT ב-Authorization header בכל בקשה לשרתי ה-API השונים.


נעבור על הקוד של השרת. נתחיל מנקודת הכניסה ל-API, ב-routes.


/routes/index.js

בקשה מסוג POST לנתיב login, בה מועברים אימייל וסיסמא. בהמשך נעשה אימות מול בסיס הנתונים (הפונקציה login), קבלת תפקיד (manageRoles) וקבלת JWT token (הפונקציה sign).


במידה והכל תקין, השרת מחזיר כתובת URL עם token.


* נא לא להתייחס ל-URL-ים שרשומים בצורה כזאת hard-coded, בהמשך אני אוסיף אותם כמשתני מערכת או בצורה אחרת. נגיע לזה.


אימות מול בסיס הנתונים:

/process/db-handler.js

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

בהמשך נעשה שימוש ב-Bcrypt לצורך השוואה בין הסיסמא שנשלחה לסיסמא מבסיס הנתונים ואימות המשתמש.


קבלת תפקיד:

/process/roles-handler.js

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


קבלת token:

/process/jwt-handler.js

כאן אני עושה שימוש בספריה jsonwebtoken לצורך יצירת ה-token. יצרתי זוג מפתחות SSL מסוג RSA. בעזרת המפתח הפרטי - בשילוב עם ה-payload ופרמטרים נוספים - אני יוצר את ה-JWT token. בשרת אחר שמבצע אימות של ה-JWT שנוצר כאן, נשתמש במפתח הציבורי לשם כך.


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


את מפתחות ה-SSL יצרתי כך:

$ openssl rsa -in node-auth-private.key -pubout -outform PEM -out node-auth-public.key


נעבור ל-Dockerfile:

שימו לב שהגדרתי את המפתח הפרטי כקובץ בתיקיית tmp בקונטיינר שעתיד להיווצר. בהמשך - ב-compose - אני עושה מיפוי בין הנתיב של המפתח במכונה המארחת לבין הנתיב שהוגדר בקונטיינר. המשתנים: ISSUER, SUBJECT, AUDIENCE, EXPIRESIN הם פרמטרים שיתווספו ל-JWT, לצורך אימות נוסף, זיהוי וניתוב.


נעבור ל-docker-compose.yml:

ב-setup הנוכחי אני משתמש ב-container פשוט של mongo ולא בשירות כלשהו בענן.

שימו לב למשתנה הבא שנמצא ב-Dockerfile:

הערך mongo בכתובת הוא שם ה-service שב-docker-compose ואותו ערך שנמצא תחת depends_on. זה חשוב על מנת להימנע בשימוש בכתובות IP וכו'.


בשירות node-auth, ה-docker image נבנה (שדה build) על-פי ההגדרות מה-Dockerfile שנמצא בתיקיה node-auth. השדה volumes מציין את המיפוי לקבצי ה-SSL. השדה ports חושף את השירות לעולם באמצעות מיפוי הפורט של הקונטיינר לפורט של המכונה המארחת. אותו פורט במקרה שלנו. מעבר לזה, כפי שהוזכר, השירות שלנו מותנה בריצה של השירות mongo (השדה depends_on).



מעבר למערכת המתאימה ובדיקת תקינות ה-token

בעת מעבר למערכת המתאימה לאחר ה-login או בעת ריענון הדפדפן של המערכת הזאת - נעשית בדיקה אם קיים JWT ב-URL או ב-localStorage (לאחר שנעשה חיבור בעבר). אם קיים, מתבצעת שליחה של בקשת verify לשרת auth כשה-JWT Token ב-Authorization header.


הקוד שצירפתי למעלה השתנה בצורה הבאה.


/routes/index.js


הוספתי קריאת GET ל-verify. אני גוזר את ה-JWT מה-header, מעביר לבדיקה בפונקציה verify שב-jwt-handler ומחזיר תשובה בהתאם. הוספתי גם status(500) במקרה של error.


/process/jwt-handler.js

בדיקה מול הפונקציה verify של jsonwebtoken. שימו לב שהפעם אני משתמש במפתח הציבורי.


הוספתי ל-DockerFile את ההגדרה המתאימה למפתח הציבורי.


צד-לקוח (frontend)

מכיל דפי הרשמה, כניסה, שחזור סיסמא וכולי. החלטתי לכתוב אותו ב-Vanilla Javascript בשימוש עם Web Components, כרגע ללא bundler. בינתיים, בזמן כתיבת הפוסט, יש רק דף כניסה.


פוסט נוסף בנושא: כתיבת Web Components

ה-repo נמצא כאן: https://github.com/YanivOr/auth-pages


index.html

לפני שנמשיך, אני רוצה להתעכב כאן על איזה עניין קטן. הקובץ environment.js. זה קובץ הקונפיגורציה של dev. הקובץ של production נקרא: environment.prod.js. יש כאן בעיה עם העובדה שכתבתי hard-coded בצורה כזאת. והנה סיבה מספיק טובה להכניס שימוש ב-bundler כלשהו. בהמשך.


נעבור לשימוש ב-Web Components. שימו לב לתגית:

<login-component>

ולקובץ Javascript שמגדיר אותה:

login-component.js

הסבר כללי קצר. בעולם ה-Web Components ישנו מושג חשוב מאוד שנקרא Shadow DOM שיוצר מעטפת, תחום, scope ל-DOM ששייך לקומפוננט שאנחנו יוצרים. יוצר מעין עץ DOM פרטי כך שהגדרות CSS והגדרות אחרות חיצוניות לא ישפיעו עליו.


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


אני משתמש בפונקציה fetch לצורך שליחת בקשות לשרת API. שימו לב שאני עובד גם עם async ו-await ב-Vanilla Javascript ללא פוליפיל וכולי. לא כל כך אכפת לי אם זה יעבוד או לא בכל הדפדפנים למרות שעושה רושם שיש כבר תמיכה בכולם.


במידה וחזרה תשובה תקינה מהשרת בצורת URL עם JWT token, הדפדפן יעשה העברה (redirect) למערכת המתאימה.


ל-docker-compose.yml התווספו ההגדרות ל-auth-pages והוא נראה כך:

ה-docker image נבנה על-פי ההגדרות שב-Dockerfile בתיקיית auth-pages. פורט 80 של הקונטיינר ממופה ל-8080 של המארח ודרכו נעשית התקשורת עם העולם החיצון.


בסוף הסרטון ניתן לראות שהדפדפן אכן עושה redirect לכתובת:

http://127.0.0.1:8081/?token=eyJhbGciOiJ...

למערכת שמיועדת למשתמש מהסוג root.


בהמשך נוסיף תמיכה ב-HTTPS ו-SSL.


זה הזמן לעבור למערכת הזאת:

מערכת מבוססת microservices - חלק 2- מערכת ניהול - React



© 2023 by DO IT YOURSELF. Proudly created with Wix.com