הוספה של תמיכת CORS לשרת proxy של API

כרגע מוצג התיעוד של Apigee Edge.
כניסה למסמכי התיעוד של Apigee X.
מידע

CORS (שיתוף משאבים בין מקורות) הוא מנגנון סטנדרטי שמאפשר לקריאות של JavaScript מסוג XMLHttpRequest (XHR) שמופעלות בדף אינטרנט לבצע אינטראקציה עם משאבים מדומיינים שאינם מקור. CORS היא פתרון נפוץ שמיושם לגבי "מדיניות מקור זהה" שנאכפת על ידי כל הדפדפנים. לדוגמה, אם מבצעים קריאת XHR ל-API של Twitter מקוד JavaScript שמופעל בדפדפן, היא תיכשל. הסיבה לכך היא שהדומיין שמציג את הדף בדפדפן שלך אינו זהה לדומיין שמציג את ממשק ה-API של Twitter. ב-CORS יש אפשרות לספק פתרון לבעיה הזו על ידי כך שהיא מאפשרת לשרתים "להביע הסכמה" אם הם רוצים לשתף משאבים בין מקורות.

סרטון: צפה בסרטון קצר כדי ללמוד איך להפעיל את CORS בשרת proxy ל-API.

תרחיש שימוש אופייני ל-CORS

הקוד הבא של JQuery מפעיל שירות יעד פיקטיבי. אם היא מופעלת מתוך הקשר של דפדפן (דף אינטרנט), היא תיכשל בגלל מדיניות המקור הזהה:

<script>
var url = "http://service.example.com";
$(document).ready(function(){
  $("button").click(function(){
    $.ajax({
        type:"GET",
        url:url,
        async:true,
        dataType: "json",
           success: function(json) {
              // Parse the response.
              // Do other things.
           },
           error: function(xhr, status, err) {
              // This is where we end up!
            }
    });
  });
});
</script>

אחד מהפתרונות לבעיה הוא ליצור שרת proxy של Apigee API שקורא ל-Service API בקצה העורפי. חשוב לזכור ש-Edge נמצא בין הלקוח (במקרה הזה, דפדפן) לבין ה-API של הקצה העורפי (השירות). שרת ה-proxy של ה-API פועל בשרת ולא בדפדפן, ולכן הוא יכול לקרוא לשירות בהצלחה. לאחר מכן, כל מה שצריך לעשות הוא לצרף כותרות CORS לתגובה של TargetEndpoint. כל עוד הדפדפן תומך ב-CORS, הכותרות האלה מאותתות לדפדפן שאפשר "להרגיע" את מדיניות המקור הזהה וכך הקריאה ל-API ממקורות שונים תצליח.

אחרי שיוצרים את שרת ה-proxy עם תמיכה ב-CORS, אפשר לקרוא לכתובת ה-URL של שרת ה-API של שרת ה-API במקום לשירות לקצה העורפי בקוד בצד הלקוח. לדוגמה:

<script>
var url = "http://myorg-test.apigee.net/v1/example";
$(document).ready(function(){
  $("button").click(function(){
    $.ajax({
        type:"GET",
        url:url,
        async:true,
        dataType: "json",
           success: function(json) {
              // Parse the response.
              // Do other things.
           },
           error: function(xhr, status, err) {
              // This time, we do not end up here!
            }
    });
  });
});
</script>

הוספה של מדיניות הוספת CORS לשרת proxy חדש ל-API

אפשר להוסיף תמיכת CORS לשרת API על ידי צירוף מדיניות Add CORS לשרת ה-API של שרת ה-proxy בזמן היצירה שלו. כדי להוסיף את המדיניות הזו, צריך לסמן את התיבה Add CORS headers (הוספת כותרות CORS) בדף 'אבטחה' באשף Build a Proxy.

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

ניתן להוסיף מדיניות CORS ל-navigator בקטע &#39;מדיניות&#39; ומצורפת לתהליך היצירה מראש של תגובת TargetEndpoint מצד שמאל

המדיניות 'הוספת CORS' מיושמת כמדיניות של assignMessage, שמוסיפה את הכותרות המתאימות לתשובה. בעיקרון, הכותרות מאפשרות לדפדפן לדעת עם אילו מקורות הוא ישתף את המשאבים שלו, באילו שיטות הוא יקבל אותן וכו'. אפשר לקרוא מידע נוסף על כותרות CORS האלה בהמלצה של W3C לשיתוף משאבים בין מקורות.

צריך לשנות את המדיניות באופן הבא:

  • מוסיפים את הכותרות content-type ו-authorization (הנדרשות לתמיכה באימות בסיסי או ב-OAuth2) לכותרת Access-Control-Allow-Headers, כפי שמוצג בקטע הקוד שבהמשך.
  • במהלך אימות OAuth2, ייתכן שיהיה צורך לבצע פעולות מסוימות כדי לתקן התנהגות שלא תואמת ל-RFC.
  • מומלץ להשתמש ב-<Set> כדי להגדיר את כותרות CORS במקום <Add>, כפי שמוצג בקטע הבא. כשמשתמשים ב-<Add>, אם הכותרת Access-Control-Allow-Origin כבר קיימת, תתקבל הודעת השגיאה הבאה:

    The 'Access-Control-Allow-Origin' header contains multiple values '*, *', but only one is allowed.

    מידע נוסף זמין במאמר שגיאת CORS : הכותרת מכילה כמה ערכים '*', '*', אבל מותר להזין רק ערך אחד.

<AssignMessage async="false" continueOnError="false" enabled="true" name="add-cors">
    <DisplayName>Add CORS</DisplayName>
    <FaultRules/>
    <Properties/>
    <Set>
        <Headers>
            <Header name="Access-Control-Allow-Origin">{request.header.origin}</Header>
            <Header name="Access-Control-Allow-Headers">origin, x-requested-with, accept, content-type, authorization</Header>
            <Header name="Access-Control-Max-Age">3628800</Header>
            <Header name="Access-Control-Allow-Methods">GET, PUT, POST, DELETE</Header>
        </Headers>
    </Set>
    <IgnoreUnresolvedVariables>true</IgnoreUnresolvedVariables>
    <AssignTo createNew="false" transport="http" type="response"/>
</AssignMessage>

הוספת כותרות CORS לשרת proxy קיים

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

טיפול בבקשות קדם-הפעלה של CORS

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

בדרך כלל, בקשות קדם-הפעלה של CORS מתבצעות באמצעות השיטה של אפשרויות HTTP. כששרת שתומך ב-CORS מקבל בקשת OPTIONS, הוא מחזיר ללקוח קבוצה של כותרות CORS שמציינות את רמת התמיכה ב-CORS. כתוצאה מלחיצת היד הזו, הלקוח יודע מה הוא רשאי לבקש מהדומיין שאינו המקור.

למידע נוסף על קדם-ההפעלה, אפשר לעיין בהמלצה של ארגון W3C לשיתוף משאבים בין מקורות. יש גם אינספור בלוגים ומאמרים בנושא CORS שאפשר לעיין בהם.

Apigee לא כוללת פתרון מוכן מראש להפעלת CORS, אבל אפשר ליישם אותו, כפי שמתואר בקטע הזה. המטרה היא ששרת ה-proxy יבדוק בקשת OPTIONS בתהליך מותנה. שרת ה-proxy יכול לשלוח תגובה מתאימה בחזרה ללקוח.

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

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ProxyEndpoint name="default">
    <Description/>
    <Flows>
        <Flow name="OptionsPreFlight">
            <Request/>
            <Response>
                <Step>
                    <Name>add-cors</Name>
                </Step>
            </Response>
        <Condition>request.verb == "OPTIONS" AND request.header.origin != null AND request.header.Access-Control-Request-Method != null</Condition>
        </Flow>
    </Flows>

    <PreFlow name="PreFlow">
        <Request/>
        <Response/>

    </PreFlow>
    <HTTPProxyConnection>
        <BasePath>/v1/cnc</BasePath>
        <VirtualHost>default</VirtualHost>
        <VirtualHost>secure</VirtualHost>
    </HTTPProxyConnection>
    <RouteRule name="NoRoute">
        <Condition>request.verb == "OPTIONS" AND request.header.origin != null AND request.header.Access-Control-Request-Method != null</Condition>
    </RouteRule>
    <RouteRule name="default">
        <TargetEndpoint>default</TargetEndpoint>
   </RouteRule>
   <PostFlow name="PostFlow">
        <Request/>
        <Response/>
    </PostFlow>
</ProxyEndpoint>

החלקים העיקריים ב-ProxyEndpoint הזה הם:

  • השדה RouteRule נוצר ליעד NULL עם תנאי לבקשת OPTIONS. לתשומת ליבך, לא צוינה נקודת יעד. אם הבקשה OPTIONS מתקבלת וכותרות הבקשה Origin ו-Access-Control-Request-Method לא ריקות, שרת ה-proxy מחזיר מיד את כותרות ה-CORS בתגובה ללקוח (תוך עקיפה של יעד ברירת המחדל של ה'קצה העורפי' בפועל). פרטים על תנאי הזרימה ועל RouteRule אפשר למצוא במאמר תנאים עם משתני זרימה.

    <RouteRule name="NoRoute">
        <Condition>request.verb == "OPTIONS" AND request.header.origin != null AND request.header.Access-Control-Request-Method != null</Condition>
    </RouteRule>
    
  • נוצר תהליך של OptionsPreFlight שמוסיף מדיניות Add CORS, שמכילה את הכותרות של CORS, לזרימה אם מתקבלת בקשת OPTIONS והכותרות של בקשות המקור ושיטת בקרת הגישה אינן ריקות.

     <Flow name="OptionsPreFlight">
                <Request/>
                <Response>
                    <Step>
                        <Name>add-cors</Name>
                    </Step>
                </Response>
            <Condition>request.verb == "OPTIONS" AND request.header.origin != null AND request.header.Access-Control-Request-Method != null</Condition>
     </Flow>
    

שימוש בפתרון CORS לדוגמה

ב-GitHub תוכלו למצוא פתרון לדוגמה של CORS, שמוטמע כתהליך משותף. מייבאים את חבילת הזרימה המשותפת לסביבה ומצרפים אותה באמצעות קטעי hook לזרימה או ישירות לתהליכי ה-proxy של ה-API. לפרטים נוספים, אפשר לקרוא את הקובץ CORS-Shared-FLow README שמצורף לדוגמה.