בנוסף לאימות משתמשים, יכול להיות שתצטרכו לאפשר לשירותים אחרים ליצור אינטראקציה עם ה-API שלכם. אפליקציות לקוח יכולות להציג למשתמשים בקשה להיכנס לאינטרנט כדי לשלוח את פרטי הכניסה שלהם, אבל צריך גישה אחרת כדי לאפשר תקשורת מאובטחת בין שירותים. בדף הזה מוצגת הגישה המומלצת שלנו להטמעת אימות בין שירותים, ומוצג קוד לדוגמה.
סקירה כללית
כדי לזהות שירות ששולח בקשות ל-API שלכם, אתם משתמשים בחשבון שירות. שירות הקריאה משתמש במפתח הפרטי של חשבון השירות כדי לחתום על אסימון אינטרנט מאובטח מסוג JSON (JWT) ושולח את ה-JWT החתום בבקשה ל-API שלכם.
כדי להטמיע אימות בין שירותים בממשק ה-API ובשירות שמבצע את הקריאה:
- יוצרים חשבון שירות ומפתח לשימוש של השירות שקורא ל-API.
- מוסיפים תמיכה באימות במסמך OpenAPI של שירות Cloud Endpoints.
מוסיפים קוד לשירות השיחות ש:
- יוצרת JWT וחותמת עליו באמצעות המפתח הפרטי של חשבון השירות.
- שליחת ה-JWT החתום בבקשה ל-API.
ESP מאמת שההצהרות ב-JWT תואמות להגדרה במסמך OpenAPI לפני שהוא מעביר את הבקשה ל-API. ESP לא בודק הרשאות של Cloud Identity שהענקתם בחשבון השירות.
דרישות מוקדמות
במאמר הזה אנחנו יוצאים מנקודת הנחה שכבר:
יצירת חשבון שירות עם מפתח
אתם צריכים חשבון שירות עם קובץ מפתח פרטי ששירות הקריאה משתמש בו כדי לחתום על ה-JWT. אם יש לכם יותר משירות אחד ששולח בקשות ל-API, אתם יכולים ליצור חשבון שירות אחד שייצג את כל השירותים ששולחים את הבקשות. אם אתם צריכים להבדיל בין השירותים – למשל, אם יש להם הרשאות שונות – אתם יכולים ליצור חשבון שירות ומפתח לכל שירות שקורא ל-API.
בקטע הזה מוסבר איך להשתמש במסוף Google Cloud ובכלי שורת הפקודה gcloud כדי ליצור את חשבון השירות ואת קובץ המפתח הפרטי, ולהקצות לחשבון השירות את התפקיד יוצר האסימונים של חשבון השירות. למידע על שימוש ב-API כדי לבצע את המשימה הזו, אפשר לעיין במאמר יצירה וניהול של חשבונות שירות.
כדי ליצור חשבון שירות ומפתח:
מסוף Google Cloud
יוצרים חשבון שירות:
במסוף Google Cloud , נכנסים לדף יצירת חשבון שירות.
בוחרים את הפרויקט שרוצים להשתמש בו.
כותבים שם בשדה Service account name.
אופציונלי: בשדה תיאור חשבון השירות, מזינים תיאור.
לוחצים על יצירה.
לוחצים על סיום.
לא לסגור את חלון הדפדפן. תשתמשו בו בשלב הבא.
יוצרים מַפְתח לחשבון השירות:
- במסוף Google Cloud , לוחצים על כתובת האימייל של חשבון השירות שיצרתם.
- לוחצים על Keys.
- לוחצים על Add key ואז על Create new key.
- לוחצים על יצירה. קובץ JSON שמכיל את המפתח הפרטי של חשבון השירות יורד למחשב.
- לוחצים על Close.
gcloud
אפשר להריץ את הפקודות הבאות באמצעות Google Cloud CLI במחשב המקומי או ב-Cloud Shell.
הגדרת חשבון ברירת המחדל ל-
gcloud. אם יש לכם יותר מחשבון אחד, חשוב לבחור את החשבון שמשויך ל Google Cloud פרויקט שבו אתם רוצים להשתמש.gcloud auth loginהצגת מזהי הפרויקטים של Google Cloud הפרויקטים שלכם.
gcloud projects listמגדירים את פרויקט ברירת המחדל. מחליפים את
PROJECT_IDבמזהה הפרויקט שרוצים להשתמש בו. Google Cloudgcloud config set project PROJECT_ID
יוצרים חשבון שירות. מחליפים את
SA_NAMEואתSA_DISPLAY_NAMEבשם ובשם לתצוגה שרוצים להשתמש בהם.gcloud iam service-accounts create SA_NAME \ --display-name "SA_DISPLAY_NAME"
מציגים את כתובת האימייל של חשבון השירות שיצרתם.
gcloud iam service-accounts listמוסיפים את התפקיד יצירת אסימונים בחשבון שירות. מחליפים את
SA_EMAIL_ADDRESSבכתובת האימייל של חשבון השירות.gcloud projects add-iam-policy-binding PROJECT_ID \ --member serviceAccount:SA_EMAIL_ADDRESS \ --role roles/iam.serviceAccountTokenCreator
יוצרים קובץ מפתח של חשבון שירות בספריית העבודה הנוכחית. מחליפים את
FILE_NAMEבשם שרוצים לתת לקובץ המפתח. כברירת מחדל, הפקודהgcloudיוצרת קובץ JSON.gcloud iam service-accounts keys create FILE_NAME.json \ --iam-account SA_EMAIL_ADDRESS
מידע נוסף על הפקודות הקודמות מופיע בgcloudחומר העזר.
מידע על שמירה על המפתח הפרטי מופיע במאמר שיטות מומלצות לניהול פרטי כניסה.
הגדרת ה-API כך שיתמוך באימות
כדי להפעיל אימות של חשבון שירות בשירותים שקוראים לשער, צריך לשנות את אובייקטי האבטחה במסמך OpenAPI כדי ש-ESP יאמת את הטענות ב-JWT החתום. השינויים יהיו שונים בהתאם לגרסה של מפרט OpenAPI שבה נעשה שימוש.
OpenAPI 2.0
- מוסיפים את חשבון השירות כגורם מנפיק במפרט OpenAPI:
securityDefinitions: DEFINITION_NAME: authorizationUrl: "" flow: "implicit" type: "oauth2" x-google-issuer: "SA_EMAIL_ADDRESS" x-google-jwks_uri: "https://www.googleapis.com/robot/v1/metadata/x509/SA_EMAIL_ADDRESS"
- מחליפים את
DEFINITION_NAMEבמחרוזת שמזהה את הגדרת האבטחה הזו. אפשר להחליף אותו בשם של חשבון השירות או בשם שמזהה את השירות שמבצע את הקריאה. - מחליפים את
SA_EMAIL_ADDRESSבכתובת האימייל של חשבון השירות. - אפשר להגדיר כמה הגדרות אבטחה במפרט OpenAPI, אבל לכל הגדרה צריך להיות
x-google-issuerשםx-google-issuerשונה. אם יצרתם חשבונות שירות נפרדים לכל שירות שיחות, תוכלו ליצור הגדרת אבטחה לכל חשבון שירות, למשל:securityDefinitions: service-1: authorizationUrl: "" flow: "implicit" type: "oauth2" x-google-issuer: "service-1@example-project-12345.iam.gserviceaccount.com" x-google-jwks_uri: "https://www.googleapis.com/robot/v1/metadata/x509/service-1@example-project-12345.iam.gserviceaccount.com" service-2: authorizationUrl: "" flow: "implicit" type: "oauth2" x-google-issuer: "service-2@example-project-12345.iam.gserviceaccount.com" x-google-jwks_uri: "https://www.googleapis.com/robot/v1/metadata/x509/service-2@example-project-12345.iam.gserviceaccount.com"
- מחליפים את
- אפשר גם להוסיף
x-google-audiencesלקטעsecurityDefinitions. אם לא מוסיפים אתx-google-audiences, ESP דורש שהתביעה"aud"(קהל) ב-JWT תהיה בפורמטhttps://SERVICE_NAME, כאשר SERVICE_NAME הוא השם של שירות ה-ESP שהגדרתם בשדהhostבמסמך OpenAPI. - מוסיפים קטע
securityברמה העליונה של הקובץ (לא מוזח או מקונן) כדי להחיל אותו על ה-API כולו, או ברמת השיטה כדי להחיל אותו על שיטה ספציפית. אם משתמשים בקטעיsecurityברמת ה-API וברמת השיטה, ההגדרות ברמת השיטה מבטלות את ההגדרות ברמת ה-API.security: - DEFINITION_NAME: []
- מחליפים את
DEFINITION_NAMEבשם שבו השתמשתם בקטעsecurityDefinitions. - אם יש יותר מהגדרה אחת בקטע
securityDefinitions, מוסיפים אותן בקטעsecurity. לדוגמה:security: - service-1: [] - service-2: []
- מחליפים את
- מבצעים פריסה של מפרט OpenAPI מעודכן. לפני ש-ESP מעביר בקשה ל-API שלכם, הוא בודק את הדברים הבאים:
- החתימה של ה-JWT באמצעות המפתח הציבורי, שנמצא ב-URI שצוין בשדה
x-google-jwks_uriבמפרט OpenAPI. - התביעה
"iss"(המנפיק) ב-JWT תואמת לערך שצוין בשדהx-google-issuer. - התביעה
"aud"(קהל) ב-JWT מכילה את שם שירות ה-ESP או תואמת לאחד מהערכים שציינתם בשדהx-google-audiences. - שהתוקף של הטוקן לא פג באמצעות הטענה
"exp"(זמן התפוגה).
- החתימה של ה-JWT באמצעות המפתח הציבורי, שנמצא ב-URI שצוין בשדה
OpenAPI 3.x
- מוסיפים את חשבון השירות כגורם מנפיק במפרט OpenAPI:
components: securitySchemes: SCHEME_NAME: type: oauth2 flows: implicit: authorizationUrl: "" scopes: {} x-google-auth: issuer: SA_EMAIL_ADDRESS jwksUri: https://www.googleapis.com/robot/v1/metadata/x509/SA_EMAIL_ADDRESS audiences: - 848149964201.apps.googleusercontent.com - 841077041629.apps.googleusercontent.com jwtLocations: - header: Authorization valuePrefix: "Bearer " security: - SCHEME_NAME: []
- מחליפים את
SCHEME_NAMEבמחרוזת שמזהה את תוכנית האבטחה הזו. אפשר להחליף אותו בשם של חשבון השירות או בשם שמזהה את השירות שמבצע את הקריאה. - מחליפים את
SA_EMAIL_ADDRESSבכתובת האימייל של חשבון השירות. - אפשר להגדיר כמה תוכניות אבטחה במפרט OpenAPI, אבל לכל הגדרה צריך להיות
issuerשונה. אם יצרתם חשבונות שירות נפרדים לכל שירות שיחות, תוכלו ליצור הגדרת אבטחה לכל חשבון שירות, למשל:components: securitySchemes: service-1: type: oauth2 flows: implicit: authorizationUrl: "" scopes: {} x-google-auth: issuer: "service-1@example-project-12345.iam.gserviceaccount.com" jwksUri: https://www.googleapis.com/robot/v1/metadata/x509/service-1@example-project-12345.iam.gserviceaccount.com jwtLocations: - header: Authorization valuePrefix: "Bearer " service-2: type: oauth2 flows: implicit: authorizationUrl: "" scopes: {} x-google-auth: issuer: "service-2@example-project-12345.iam.gserviceaccount.com" jwksUri: "https://www.googleapis.com/robot/v1/metadata/x509/service-2@example-project-12345.iam.gserviceaccount.com" jwtLocations: - header: Authorization valuePrefix: "Bearer "
- מחליפים את
- אפשר גם להוסיף
audiencesלקטעsecuritySchemes. אם לא מוסיפים אתaudiences, ESP דורש שהתביעה"aud"(קהל) ב-JWT תהיה בפורמטhttps://SERVICE_NAME, כאשר SERVICE_NAME הוא השם של שירות ה-ESP שהגדרתם בשדהhostבמסמך OpenAPI. - מוסיפים קטע
securityברמה העליונה של הקובץ (לא מוזח או מקונן) כדי להחיל אותו על ה-API כולו, או ברמת השיטה כדי להחיל אותו על שיטה ספציפית. אם משתמשים בקטעיsecurityברמת ה-API וברמת השיטה, ההגדרות ברמת השיטה מבטלות את ההגדרות ברמת ה-API.security: - SCHEME_NAME: []
- מחליפים את SCHEME_NAME בשם שבו השתמשתם בקטע
securitySchemes. - אם יש יותר מהגדרה אחת בקטע
securitySchemes, מוסיפים אותן בקטעsecurity. לדוגמה:security: - service-1: [] - service-2: []
- מחליפים את SCHEME_NAME בשם שבו השתמשתם בקטע
- מבצעים פריסה של מפרט OpenAPI מעודכן. לפני ש-ESP מעביר בקשה ל-API שלכם, הוא בודק את הדברים הבאים:
- החתימה של ה-JWT באמצעות המפתח הציבורי, שנמצא ב-URI שצוין בשדה
jwksUriבמפרט OpenAPI. - התביעה
"iss"(המנפיק) ב-JWT תואמת לערך שצוין בשדהissuer. - שהטענה
"aud"(קהל) ב-JWT מכילה את שם שירות ה-ESP שלכם או תואמת לאחד מהערכים שציינתם בשדהaudiences. - שהתוקף של הטוקן לא פג באמצעות הטענה
"exp"(זמן התפוגה).
- החתימה של ה-JWT באמצעות המפתח הציבורי, שנמצא ב-URI שצוין בשדה
שליחת בקשה מאומתת ל-Endpoints API
כדי לשלוח בקשה מאומתת, השירות שקורא ל-API שולח JWT שחתום על ידי חשבון השירות שצוין במסמך OpenAPI. שירות השיחות צריך:
- יוצרים JWT וחותמים עליו באמצעות המפתח הפרטי של חשבון השירות.
- שולחים את ה-JWT החתום בבקשה ל-API.
בדוגמת הקוד הבאה מוצג התהליך הזה עבור שפות נבחרות. כדי לשלוח בקשה מאומתת בשפות אחרות, אפשר לעיין בjwt.io כדי לראות רשימה של ספריות נתמכות.
-
בשירות הקורא, מוסיפים את הפונקציה הבאה ומעבירים לה את הפרמטרים הבאים:
Java -
saKeyfile: הנתיב המלא לקובץ המפתח הפרטי של חשבון השירות. -
saEmail: כתובת האימייל של חשבון השירות. -
audience: אם הוספתם את השדהx-google-audiencesלמסמך OpenAPI, צריך להגדיר אתaudienceלאחד מהערכים שציינתם עבורx-google-audiences. אחרת, מגדירים אתaudienceל-https://SERVICE_NAME, כאשרSERVICE_NAMEהוא שם השירות של Endpoints. -
expiryLength: זמן התפוגה של JWT, בשניות.
Python -
sa_keyfile: הנתיב המלא לקובץ המפתח הפרטי של חשבון השירות. -
sa_email: כתובת האימייל של חשבון השירות. -
audience: אם הוספתם את השדהx-google-audiencesלמסמך OpenAPI, צריך להגדיר אתaudienceלאחד מהערכים שציינתם עבורx-google-audiences. אחרת, מגדירים אתaudienceל-https://SERVICE_NAME, כאשרSERVICE_NAMEהוא שם השירות של Endpoints. -
expiry_length: זמן התפוגה של JWT, בשניות.
Go -
saKeyfile: הנתיב המלא לקובץ המפתח הפרטי של חשבון השירות. -
saEmail: כתובת האימייל של חשבון השירות. -
audience: אם הוספתם את השדהx-google-audiencesלמסמך OpenAPI, צריך להגדיר אתaudienceלאחד מהערכים שציינתם עבורx-google-audiences. אחרת, מגדירים אתaudienceל-https://SERVICE_NAME, כאשרSERVICE_NAMEהוא שם השירות של Endpoints. -
expiryLength: זמן התפוגה של JWT, בשניות.
הפונקציה יוצרת JWT, חותמת עליו באמצעות קובץ המפתח הפרטי ומחזירה את ה-JWT החתום.
Java Python Go -
-
בשירות שמבצע את הקריאה, מוסיפים את הפונקציה הבאה כדי לשלוח את ה-JWT החתום בכותרת
Authorization: Bearerבבקשה ל-API:Java Python Go
כששולחים בקשה באמצעות JWT, מטעמי אבטחה מומלץ להוסיף את אסימון האימות לכותרת Authorization: Bearer. לדוגמה:
curl --request POST \
--header "Authorization: Bearer ${TOKEN}" \
"${ENDPOINTS_HOST}/echo"
כאשר ENDPOINTS_HOST ו-TOKEN הם משתני סביבה שמכילים את שם המארח של ה-API ואת טוקן האימות, בהתאמה.
קבלת תוצאות מאומתות ב-API
בדרך כלל ספקי ESP מעבירים את כל הכותרות שהם מקבלים. עם זאת, הוא מחליף את הכותרת המקורית Authorization כשכתובת ה-Backend מצוינת על ידי x-google-backend במפרט OpenAPI או על ידי BackendRule בהגדרת שירות gRPC.
ספק ה-ESP ישלח את תוצאת האימות ב-X-Endpoint-API-UserInfo
ל-API של השרת העורפי. מומלץ להשתמש בכותרת הזו במקום בכותרת המקורית Authorization. הכותרת הזו היא מחרוזת שbase64urlמקודדת אובייקט JSON. פורמט אובייקט ה-JSON שונה בין ESPv2 לבין ESP.
ב-ESPv2, אובייקט ה-JSON הוא בדיוק המטען הייעודי (payload) המקורי של ה-JWT. ב-ESP, אובייקט ה-JSON משתמש בשמות שדות שונים ומציב את מטען ה-JWT המקורי בשדה claims.
מידע נוסף על הפורמט זמין במאמר בנושא טיפול ב-JWT בשירות לקצה העורפי.