2013-07-27

האם אנו זקוקים ל Software Design?

ביצוע תכנון (Software Design) מתחבר לאנשים רבים ל"עשיית הדבר הנכון".
אם את/אתה אחד מאותם אנשים, עצרו לרגע והסבירו: מדוע זה הדבר הנכון? מה יקרה אם לא נעשה Design?

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

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





מעט ספקות

אנחנו לא באמת יודעים מה היה קורה "אילו היינו עושים..." - אנחנו יכולים רק לדמיין: "אם היינו משקיעים במניית אפל בתחילת 2007 ומוכרים אותה ברבעון הראשון של 2012 היינו עושים 600% רווח!". במקבילה שלנו בעולם התכונה: "אם היינו שומרים את כל ה properties על אובייקט ה xyz זה היה פתרון אחד לשלושת הלקוחות ולא היינו מסתבכים ככה!".

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

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

כל זה השתנה לא מעט בעשור האחרון בעזרת תנועת האג'ייל שעזרה לחדד 2 נקודות:
  • לא כל השקעה ב Design מחזירה את עצמה. המלצה: השקיעו בזהירות רבה יותר (על זה בהמשך).
  • אנו יכולים להפחית את עלות השינויים במערכת production (ע"י אוטומציה, למשל) ולכן שווה אולי לדחות רבות מהחלטות ה Design לשלב מאוחר יותר - שלב בו הידע שלנו לגבי צרכי המערכת יהיה טוב יותר. 


עברו 30-40 שנה. מה השתנה?

מאז שהחלו להתעסק בצורה מקיפה ברעיונות של Software Design עברו קצת מים בנהר, ונוספו כמה כלים חדשים ומשמעותיים המקלים על פיתוח תוכנה גדולה ומורכבת:
  • Unit Tests & Automation
  • Agile (ספרינטים קצרים, MVCs)
  • Static Analysis Tools, Modern IDEs
  • מערכת ניהול קוד מבוזרת, כלי build מתקדמים (כמו Gradle [א]) ו Continuous Integration
  • ווב, מחשוב ענן, Continuous Delivery or Deployment 

האם כלים אלו:
  1. מעלים את רף מורכבות התוכנה בה תכנון (Design) הוא משתלם?
  2. מייתרים את הצורך ב Design?
  3. הופכים את ה Design למשהו אחר לגמרי?
  4. או בכלל לא משנים?

מה דעתכם?



הכלכלה של ה Design


את הטיעון "הכלכלי" המצדיק השקעה ב Software Design היה מקובל להציג לאורך השנים בדרכים שונות:

שנות ה 60 עד שנות ה 90: "נדל''ן יכול רק לעלות"



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


שנות ה 2000: "הבורסה היא השקעה בטוחה לאורך זמן, עם תשואה צפויה של 6% בשנה"

עם הזמן הוכרה ההבנה שלעתים אי-ביצוע Design אינו מתכון בטוח לכישלון. המודל ה"כלכלי" התאים את עצמו והתמקד בתובנה ש Design משתלם - בטווח הארוך:

מקור: הבלוג של מרטין פאולר

כמה הבחנות חשובות שנוספו הן:
  • הטענה שהשקעה ב Design משתלמת היא תחושה (של רבים ומקצועיים) - אך לא עובדה מוכחת.
  • יש אי-הסכמה בקרב המקצוענים מתי Design משתלם: אחרי שבועות של פיתוח או חודשים רבים.
  • יש אבחנה ברורה בין "good design" ל "mediocre design" - פעולת ה Design לא מבטיחה תוצאות, התוצאות תלויות מאוד באיכות העבודה.

לינק רלוונטי: Design Stamina Hypothesis 



שנות העשרה: "פיזור תיק ההשקעות"
היום, מקובל יותר להציג את תהליך ה Design כתהליך של ניהול סיכונים: לכל עבודת Design יש מחיר: מחיר ביצוע ה Design עצמו (שהוא לרוב החלק הקטן) ומחיר הביצוע (שהוא לרוב החלק הגדול). מחיר הביצוע כולל מפתחים שצריכים להתמודד עם מגבלות שהציב עליהם ה Design, וגם אכיפה שאכן מגבלות אלו נאכפות בפועל.


גם אם Design נעשה בצורה טובה (קרי: מימוש נכון של העקרונות החשובים), עדיין כדאי למצוא את התמהיל הנכון של כמה חוקים לאכוף, והיכן "לשחרר".

תובנות חדשות שנוספו הן:
  • Design כולל סדרה של החלטות שניתן לבחור ליישם רק חלק מהן. נכון, יש קשר ותלות בין ההחלטות - אך תלות זו עדיין מותירה החלטות שניתן לוותר עליהן באופן בלתי-תלוי.
  • Design הוא תהליך מתמשך. המתכנת מבצע Design (בקטנה) תוך כדי שהוא כותב קוד, וכדאי פעם בכמה זמן (חודש-חודשיים?) לבצע עצירה מתודית ולבדוק את ה Design וכיצד הוא מתפתח בצורה יותר שיטתית.
  • חשוב לבחון כל החלטה של Design כהשקעה עם סיכון: "בניית שכבת ההפשטה תדרוש עוד x התעסקות, אבל תקל מאוד במידה ובעתיד יוסיפו אלמנט חדש". מה הסבירות שיוסיפו אלמנט חדש בעתיד? עד כמה יותר יקר יהיה לבצע את השינוי בעתיד? אולי סבירות נמוכה שהאירוע יתרחש מצדיקה תשלום כפול בעתיד על אותו האלמנט.

לינק רלוונטי: ?Is Design Dead



סיכום

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

תכנון הוא סוג של השקעה / ניהול סיכונים: אנחנו משקיעים קצת היום ומקווים להנות מפירות ה design בעתיד. לפעמים העתיד הוא חודש הבא ("תכנון מקומי") ולפעמים בעוד שנה-שנתיים ("ארכיטקטורה").

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

כדי שנוכל להחליט טוב יותר כמה מה Design ליישם ומתי, שווה לבחון בצורה יסודית את השאלות "מה בעצם Design פותר - בפועל?" ו "מה הן בעצם המשמעויות של החלטות ה Design שאנו עושים?" - שאלות לא פשוטות.

קוראים קבועים ישימו לב שאני תוקף לאחרונה את כל הנושא, אולם לאט ובזהירות. היעד הבא הוא להתמודד עם 2 השאלות הנ"ל.

שיהיה לנו בהצלחה!

שאלות, תגובות, מחשבות וכו' - יתקבלו בשמחה!



----

[א] ב Technology Radar האחרון (מאי 2013) סימנו את Mavan ב "Hold" (תרגם לעברית: "צמצמו השקעות קיימות והימנעו משימוש בו בעתיד"), בעוד Gradle זכה ב "Adopt".



2013-07-20

אבני הבנין של האינטרנט: SOP ותקשורת Cross-Domain


כלל ה Same Origin Policy, או בקיצור SOP, הופיע לראשונה בדפדפן Netscape 2.0. הוא הופיע בד-בבד עם שפת התכנות JavaScript וה (Document Object Model (DOM. כמו JavaScript וה DOM, הוא הפך לחלק בלתי-נפרד מהדפדפן המודרני.

SOP הוא עקרון האבטחה, כנראה המרכזי ביותר בדפדפן. הרעיון הוא דיי פשוט: שני Execution Contexts שונים של ג'אווהסקריפט לא יוכלו לגשת על ה DOM אחד של השני אם הם לא מגיעים מאותו המקור (origin).
מתי יש לנו Execution Contexts שונים? כאשר יש iFrames שונים או טאבים שונים.

איך מוגדר origin? שילוב של שלושה אלמנטים:
  • סכמה / פרוטוקול (למשל http או https)
  • Fully Qualified Domain Name (בקיצור: FQDN, למשל news.google.co.il)
  • מספר port [א].

SOP מונע מצב בו פתחנו 2 חלונות: אחד לאתר לגיטימי והשני לאתר זדוני (בטעות), והאתר הזדוני עושה באתר הלגיטימי כרצונו. בנוסף יש לנו כלי בשם iFrame - כלי ל isolation, כך שאנו יכולים לפתוח בדף שלנו iFrame לאתר חיצוני לא מוכר ולדעת שאנו מוגנים בפניו.

עולם האינטרנט השתנה מאז 1995, והיום יש הרבה יותר אינטראקציה משותפת (mashups) בין אתרים שונים. SOP מגביל את היכולת שלנו לשתף מידע בין מקורות שונים.


אז מה עושים? האם נחרץ גורלו של האתר שלנו להיות מוגן, אך מבודד - לעד?

בפוסט זה נבין מה מדיניות ה SOP בדיוק אומרת, ונסקור מספר טכניקות נפוצות לבצע שיתוף מידע.


פוסט זה שייך לסדרה אבני הבניין של האינטרנט.






תזכירו לי מה המשמעות של SOP?


נניח שדף ה HTML של האפליקציה / אתר שלנו נטען מהכתובת http://www.example.com/home/index.html.
משמע: ה origin הנוכחי שלנו הוא http://www.example.com:80.


הנה המשמעות של מדיניות ה SOP בנוגע לגישה ל origins אחרים ("Compared URL"):

מקור: http://en.wikipedia.org/wiki/Same_origin_policy

הערה מעניינת: SOP, כמו מנגנוני הגנה אחרים של הדפדפן, מתבססים על ה FQDN ללא אימות מול ה IP Address. המשמעות היא שפריצה לשרת DNS יכולה להשבית את ה SOP ולאפשר קשת רחבה ויצירתית של התקפות על המשתמש.


SOP הוגדר במקור עבור גישה ל DOM בלבד, אך עם השנים הוא הורחב:

XMLHttpRequest (המשמש לקריאות Ajax) מוגבל גם הוא. קריאות Ajax יוגבלו רק ל origin ממנו נטען המסמך (כלומר: קובץ ה HTML). בנוסף, בעקבות היסטוריה של התקפות שהתבססו על "זיוף" קריאות ל origin הלגיטימי עם Headers מטעים - נוספו מספר מגבלות על Headers אותם ניתן לשנות בקריאות Ajax (למשל: Referer או Content-Length וכו').

Local Storage (יכולת חדשה ב HTML5 לשמור נתונים באופן קבוע על הדפדפן) גם היא מוגבלת ע"פ ה SOP. לכל origin יש storage שלו ולא ניתן לגשת ל storage של origin אחר.

Cookies - האמת שמגבלות על Cookies החלו במקביל להתפתחות ה SOP. התוצאה: סט מגבלות דומה, אך מעט שונה. הדפדפן לא ישלח Cookies לדומיין אחר (למשל evil.com) אך הוא ישלח את ה cookies לתת-domain למשל:
evil.www.example.com, תת-domain של www.example.com.
בדפדפנים שאינם Internet Explorer, ניתן לדרוש אכיפה של domain מדויק ע"י השמטה של פרמטר ה domain (תיעוד ב MDN, קראו את התיאור של הפרמטר domain).

כיוון ש Cookie יכולים להכיל מידע רגיש מבחינת אבטחת מידע (למשל: אישור גישה לשרת), הוסיפו עליהם עוד 2 מנגנוני הגנה נוספים:
httponly - פרמטר ב HTTP header של set-cookie שהשרת יכול לסמן, המונע גישה מקוד ג'אווהסקריפט בצד-הלקוח ל cookie שטמן השרת. כלומר: ה cookie רק יעבור הלוך וחזור בין השרת לדפדפן, בלי שלקוד הג'אווהסקריפט תהיה גישה אליו.
secure - פרמטר בצד הג'אווהסקריפט של יצירת cookie (שוב, התיעוד ב MDN) שאם נקבע ל true - יגרום לדפדפן להעביר את ה cookie רק על גבי תקשורת HTTPS (כלומר: מוצפנת).

Java Applet, Flash ו Silverlight כוללים כללים שונים ומשונים הנוגעים ל SOP. חבורה זו היא זן נכחד - ולכן אני מדלג על הדיון בעניינה.


SOP מתיר חופש במקרים הבאים:

Cross domain resource loading - שימו לב, זהו כלל חשוב: הדפדפן כן מאפשר לטעון קבצים מ domains אחרים. לדוגמה: קבצי ג'אווהסקריפט, תמונות, פונטים (בעזרת font-face@) או קבצי וידאו. על קבצי CSS יש כמה מגבלות [ב]. כלומר: האתר שלנו, www.example.com יכול בלי בעיה לטעון קובץ ג'אווהסקריפט מאתר אחר suspicious.com. טעינת הג'אווהסקריפט הינה הצהרת אמון במקור - ועל כן קוד הג'אווהסקריפט שנטען מקבל את ה origin שלנו ועל כן הוא יכול לגשת ל DOM שלנו ללא מגבלה. מצד שני: הוא אינו יכול לגשת ל DOM של iFrame אחר שמקורו מ suspicious.com או לבצע קריאות Ajax ל suspicious.com - למרות שהוא נטען משם.

הערה: אשתמש מעתה לסירוגין במונחים "frame" ו "iFrame", ע"פ הביטוי הספציפי וכיצד שנהוג להשתמש בו. לצורך הדיון בפוסט זה, הכוונה היא לאותו הדבר.

Location - סקריפט שמגיע מ origin שונה מורשה לבצע פעולות השמה (אך לא קריאה) על אובייקט ה Location (ה URL הנוכחי של ה frame) כגון ()location.replace. זה נשמע מעט מוזר, אך הסיבה לכך היא לאפשר שימוש בטכניקה בשם iFrame busting הנועדה להילחם בהתקפה בשם clickjacking. כלומר: כדי להבטיח שאתר זדוני לא מארח את האתר שלנו ומראה רק חלקים מסוימים ממנו כחלק מהונאה, מותר לנו לגשת לכל frame אחר בדף (למשל ה Top Frame) ולהפוך אותו לכתובת האתר שלנו. התוצאה: האתר המארח יוחלף באתר שלנו - ללא אירוח.
דרך מודרנית יותר למניעת clickjacking היא שימוש ב HTTP Header בשם X-Frame-Options.


מתקפת clickjacking בפעולה. מקור.


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



טכניקות התמודדות

עם השנים התבססו מספר טכניקות מוכרות "לעקוף" את מגבלות ה SOP, במצבים בהם אנו מעוניינים בכך. חלק מהיכולות אופשרו ע"י הדפדפנים, ואחרים ניצלו "פרצות" בחוקי ה SOP בכדי לבצע את העבודה.

רשימת הטכניקות אותן נסקור בפוסט זה

בגדול ניתן לחלק את הטכניקות ל2 משפחות:
  1. תקשורת בין Frames מ Origins שונים באותו הדף.
  2. תקשורת בין לקוח לשרת ב Origin שונה (כלומר: קריאות Ajax).
אני אפתח במשפחה הראשונה, למרות שהשימוש בה פחות נפוץ בימנו - מכיוון שחלק מהטכניקות שלה מהוות בסיס לטכניקות במשפחה השנייה.






Cross-Origin inter-Frame Communication





להלן נסקור מספר טכניקות מקובלות כדי לתקשר בין iFrames מ origins (או domains) שונים.


Domain Relaxation - הטכניקה הזו אופשרה ע"י הדפדפנים.
בטכניקה זו קוד הג'אווהסקריפט משנה את ה origin הנוכחי ע"י השמת ערך חדש למשתנה: document.domain.
המגבלה: הוא יכול רק "לקצץ" תתי-domains ולא להחליף את ה domain לגמרי. לדוגמה:
  • מ "login.example.com" ל "example.com" - מותר.
  • מ "login.example.com" ל "login.com" - אסור.
  • כמו כן, מ "login.example.com" ל "com" - מותר, אבל מסוכן!!
התוצאה של פעולת ה domain relaxation היא הבעת אמון גדולה בכל אתר מה domain המעודכן, והרשאה לג'אווהסקריפט של אותו האתר לערוך את ה DOM שלנו. אם אפליקציה זדונית שאנו מארחים ב iFrame, מסוגלת לבצע Domain Relaxation לאותו Relaxed Domain כמו שלנו - היא יכולה לגשת ל DOM שלנו ללא מגבלה.

חשוב לציין ש Domain Relaxation לא משפיע על אובייקט ה XmlHttpRequest. קריאות Ajax יתאפשרו רק ל origin המקורי ממנו נטען ה Document שלנו.

בנוסף, בחלק מהדפדפנים Domain Relaxation ישפיע רק על גישה בין Frames באותו הטאב ולא על גישה בין טאבים נפרדים.

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


Encoding Messaged on the URL Fragment Id

טריק מלוכלך זה מתבסס על 2 עובדות:
  • SOP מתיר ל Frame אחד לשנות את ה Location (כלומר URL) של Frame כלשהו אחר בדף.
  • שינוי של FragmentID (החלק ב URL שלאחר סימן ה #) לא גורם ל reload של המסמך (כפי שהסברנו בפוסט על ה URL)
התרגיל עובד כך: פריים (frame) א' משנה את ה FID (קיצור של Fragment ID) של פריים ב'. הוא מקודד הודעה כלשהי שהוא רוצה להעביר.
פריים ב' מאזין לשינויים ב FID שלו, מפענח את ההודעה ומחזיר תשובה ע"י קידוד הודעה ע"י שינוי ה FID של פריים ב'.

וריאציה נוספת של הטכניקה הזו היא שכל פריים משנה את ה window.name של עצמו. SOP מתיר ל frames שונים לקרוא את ה property הזה מ frames אחרים ללא הגבלה.

סיכום: מלוכלך, מוגבל, לא בטוח (אני לא ידוע מי באמת שינה לי את ה FID) - אבל יכול לעבוד.
ישנן עוד כמה טכניקות דומות (בעזרת Flash, למשל) - אך אני אדלג עליהן.


Post Message


בשלב מסוים החליטו הדפדפנים לשים סוף לבאלגן. כשהרבה מפתחים משתמשים בטכניקות כגון ה Encoding על ה FID, עדיף פשוט לאפשר להם דרך פשוטה ובטוחה. כאן נכנסת לתמונה יכולת ה "Post Message", שהוצגה כחלק מ HTML5.

יכולת זו נתמכת החל מ IE8 (כולל), ועל כן היא רלוונטית עבור הרוב הגדול של הדפדפנים שבשימוש היום.

מנגנון ה Post Message (בקיצור: PM) מאפשר לשלוח הודעות טקסט בין iFrames או חלונות שונים בדפדפן, בתוספת מנגנון אבטחה שמגביל את שליחת / קבלת ההודעות ל domain ידוע מראש.

הנה דוגמה:


כדאי לציין שניתן לשלוח הודעה עם target domain של "*" ("לכל מען דבעי"). לא ברור לי מדוע איפשרו יכולת זו - אבל היא מדלגת על מנגנון האבטחה החשוב של PM. כמו כן חשוב לבצע בדיקת domain בקבלה ולבדוק רק Domain מדויק.
מימוש אפשרי הוא בדיקה חלקית, למשל:

if (msg.origin.indexOf(".example.com") != -1) { ... }

מזהים מה הבעיה פה?
תוקף מ Domain בשם example.com.dr-evil.com (השייך לחלוטין לדומיין dr-evil.com) יוכל גם הוא לשלוח לנו הודעות!

סיכום: טכניקה בטוחה וקלה לשימוש. כדאי תמיד להעדיף אותה - אם היא זמינה.

שווה לציין ספריה בשם EasyXDM לשליחת הודעות Cross Domain.
EasyXDM ישתמש ב Post Message, אם הוא זמין (IE8+) או יבצע fallback למגוון שיטות היכולות לעבוד על IE6 ו IE7 - אם אין לו ברירה. מומלץ להשתמש רק אם תמיכה ב IE6-7 היא חובה.






Cross-Origin Client-To-Server Communication




סקרנו את המשפחה הראשונה של הטכניקות: תקשורת בין Frames בדפדפן. טכניקות אלו שימושית כאשר אנו מארחים אפליקציות או widgets ב iFrame.

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

בואו נעבור על מספר טכניקות מקובלות המאפשרות לבצע קריאת Ajax ל originB - וננסה לעשות זאת בצורה מאובטחת ככל האפשר.


Server Proxy

כאשר יש לנו דף / document המשויך ל Domain A, אין לו בעיה לייצר קריאות Ajax ל Domain A - הרי זה ה origin שלו. אם אנו מנסים להוציא קריאת Ajax ל Domain B (כלומר: domain אחר), הדפדפן יחסום את הקריאה כחלק ממדיניות ה SOP:



אפשרות אחת להתמודד עם הבעיה היא לבקש מהשרת ב Domain A, לשמש עבורנו כ "Proxy" ולבצע עבורנו את הקריאה:



לשרת אין מדיניות SOP, ולכן אין לו מגבלה לתקשר מול שרת אחר שנמצא ב Domain B. אין משמעות הדבר שאין סיכון בתקשורת לשרת אחר - פשוט אין אכיפה. על שרת A לממש את מנגנוני האבטחה בעצמו ולהחליט באילו שרתים אחרים הוא בוטח.

וריאציה אחרת של שיטה זו היא להציב Reverse Proxy בין הדפדפן ל 2 השרתים. ניתן לקבוע חוקים ב Reverse Proxy שייגרמו ל-2 השרתים להראות כאילו הם באותו ה Domain.

גישת ה Server Proxy היא פשוטה, אולם יש לה כמה חסרונות:
  • השרת צריך לבצע עבודה נוספת של העברת הודעות בין הדפדפן לשרת ב' , מה שיכול בהחלט לפגוע לו ב Scalability (עוד חומרה = עוד כסף). במקרה של Reverse Proxy - העלות הנוספת היא ברורה יותר.
  • שרת ה Proxy שלנו הוא לא דפדפן, הוא לא יעביר באופן טבעי User Agent או Cookies, אלא אם נוסיף קוד שיעשה זאת.
  • "הערמנו" על הדפדפן ועל מנגנון האבטחה שלו, אבל האם יצרנו אלטרנטיבה בטוחה מספיק? (האם יצרנו בכלל אלטרנטיבה בטוחה במידה כלשהי?)
סיכום: פתרון פשוט אבל בעייתי: יש לשים לב למחיר הנוסף ב Scalability, ולוודא שלא יצרנו פרצת-אבטחה.


JSONP (קיצור של JSON with Padding)

טכניקת ה JSONP מבוססת על העובדה הבאה:
  • SOP לא מגביל אותנו לטעון קבצי JavaScript מ Domains אחרים.

מה היה קורה אם קוד הג'אווהסקריפט, שנטען מ Domain אחר, היה כולל קוד שיוצר במיוחד עבור הקריאה שלנו? למשל, קוד המבצע השמה של שורת נתונים לתוך משתנה גלובלי שאנו יכולים לגשת אליו? - משמע שהצלחנו להעביר נתונים, Cross-domain, לדפדפן!

הנה דוגמת קוד כיצד אנו קוראים מצד-הלקוח ל API מסוג JSONP:


והנה התשובה שהשרת מייצר (דינמית) = הקובץ info.js:


הערך "jsonpCallBack" הגיע כפרטמר על ה URL של ה Request, ושאר הנתונים הגיעו מהשרת (מבנה נתונים, DB וכו').
הקוד הנוסף שעוטף את ה data, בין אם זו השמה למשתנה גלובלי או קריאה ל callback ששמו נשלח - נקרא "Padding". זהו ה P בשם JSONP.
לרוב אנו נעדיף Padding מסוג callback, מכיוון ש callback מייצר trigger ברגע המידע חזר מהשרת. כאשר משתמשים ב Padding מסוג "השמה למשתנה גלובלי" אנו נאלץ לדגום את המשתנה שוב ושוב בכדי לדעת מתי הוא השתנה...

ל JSONP יש מספר חסרונות:
  • נדרשת תמיכה מהשרת: על השרת לייצר Padding מתאים לנתונים. ה Padding חייב לקרות בצד השרת ואין דרך "להשלים" אותו בצד הלקוח עבור קובץ ג'אווהסקריפט שלא כולל אותו.
  • מכיוון שלא ניתן לטעון קובץ ג'אווהסקריפט יותר מפעם אחת, אם אנו רוצים לבצע מספר קריאות JSONP יהיה עלינו לייצר שם חדש לקובץ ה script בכל קריאה = מעמסה.
  • JSNOP מוגבל לפעולות HTTP GET בלבד (לא ניתן לטעון scripts במתודת HTTP אחרת) - עובדה שמונעת מאתנו להשתמש ב JSONP כדי לקרוא ל REST API.
  • אין Error Handling. אם קובץ הג'אווהסקריפט לא נטען (404, 500 וכו') - אין לנו שום דרך לדעת זאת. פשוט לא יקרה שום דבר.
  • אבטחת מידע: השרת ממנו אני טוען את הנתונים ב JSONP לא מעביר רק נתונים - הוא מעביר קוד להרצה. אני צריך לסמוך על השרת הזה שלא ישלח לי קוד זדוני.

יתרון משמעותי של JSONP הוא הפשטות (היחסית) כאשר יש לנו קריאה בודדת והעובדה שהוא יעבוד גם עם דפדפנים ישנים. ניתן למצוא הרבה WEB APIs של חברות חשובות כגון גוגל או טוויטר - החשופים ב JSONP.

סיכום: אופציה סבירה כאשר יש תקשורת מועטה ואנו סומכים על שרת היעד.


iFrame Proxy / Relay
טכניקת ה iFrame Proxy (או iFrame Relay) מבוססת על תקשורת בין iFrames, וספציפית על Post Messages.
המנגנון עובד כך:
  1. פתח iFrame שה URL שלו מצביע עד דף (שהכנו מראש) הנמצא על הדומיין איתו אנו רוצים לתקשר. ה origin של המסמך ב iFrame יהיה אותו ה domain.
  2. הדף הנ"ל יטען קובץ ג'אווהסקריפט (שהכנו מראש), נקרא לו proxy.js.
  3. נבצע קריאות Post-Message ל iFrame שייצרנו. proxy.js יאזין לקריאות אלו, כאשר מנגנון ה Post-Message מספק לנו אבטחה.
  4. proxy.js יבצע קריאת Ajax לדומיין המרוחק, אין לו מגבלות - כי הדומיין הזה הוא ה origin שלו.
אם השרת מחזיר תשובה, היא תגיע ל Proxy.js.
  1. proxy.js מקבל את התשובה מהשרת ומבצע Post-Message בחרזה ל Frame / לקוד שלנו.
לגישת ה iFrame Proxy יש כמה חסרונות:
  • היא מורכבת למימוש.
  • נדרשת תמיכה מצד השרת.
  • היא דורשת תמיכה ב PM, קרי IE8+.

מצד שני היא טובה מבחינת Security:
  • בעזרת מנגנון ה PM אנו מוודאים ש proxy.js מגיע מה domain הרצוי.
  • proxy.js מבודד בתוך iFrame ואינו יכול להשפיע על הקוד שלנו - כך שלא חייבים לסמוך ב 100% על שרת היעד.

סיכום: אופציה מורכבת למימוש - אך טובה מבחינת Security.



CORS (קיצור של Cross Origin Resource Sharing)
מאחר ו JSONP ו iFrame Proxy הם אלתורים, החליטו הדפדפנים לפתור את בעיית הקריאה לשרת cross-domain בצורה שיטתית. התוצאה היא פרוטוקול ה CORS.

CORS מאפשר לנו בקלות יחסית לבצע קריאת "Ajax" לשרת בדומיין אחר. השרת צריך לממש מצדו את הפרוטוקול (כמה כללי התנהגות ו Headers על גבי HTTP).

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

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

גרסת ה IE הראשונה שממשה את CORS היא IE10 (וגם לו יש באג משמעותי - הוא לא שולח cookies כמו שצריך).
בפועל התמיכה ב CORS מתחילה מ IE10 אם אינכם זקוקים ל cookies, ואם אתם זקוקים ל Cookies - היא מתחילה ב IE11. סוג האפליקציות שיכולות להסתדר עם תנאי סף שכאלו הם בעיקר אפליקציות מובייל.

עדיין אפשר לבצע מימוש יותר מורכב שישתמש ב XDomainRequest ב IE8-10 וב CORS בכל שאר המקרים.

חסרונות סה"כ:
  • ה API של CORS מעט מסורבל, מימוש בצד השרת דורש מעט עבודה.
  • CORS עשוי להזדקק ל 2 HTTP Requests (מה שנקרא "preflight") בכדי להשלים קריאה בודדת. אלו הדקויות של המימוש הפנימי.
  • התמיכה של IE היא בעייתית.
ל CORS יש גם יתרונות:
  • סטנדרטי
  • אבטחה טובה
  • לא מצריך להמציא מנגנון שלם, כגון iFrame Proxy.

סיכום: אופציה טובה, שתהיה טובה יותר בעוד מספר שנים.



סיכום

למדנו על מנגנון ה Same Origin Policy, מנגנון אבטחה מרכזי בדפדפנים המגן עלינו מפני התקפות רבות, אך משפיע רבות על היכולת של האפליקציה / אתר שלנו לתקשר עם העולם.

סקרנו והשוונו את הטכניקות הנפוצות לביצוע תקשורת Cross-Domain תחת מנגנון ה SOP, טכניקות מ-2 משפחות:
  • תקשורת בין iFrame ל iFrame.
  • תקשורת מול שרת ב Domain אחר.
סה"כ, הבנת נושא ה SOP היא חשובה למדי עבור מערכות המתקשרות בין Domains שונים, צורך ההולך וגובר עם השנים.


שיהיה בהצלחה!




----

[א] דפדפן IE לא מחשיב את ה port כחלק מה origin עבור גישה ל DOM. בפועל נדירים מ המקרים בהם דף HTML ייטען מ port לא סטנדרטי.

[ב] המגבלות שונות מדפדפן לדפדפן: IE, פיירפוקס, כרום, ספארי (יש לגלול ל CVE-2010-0051) ואופרה (לפני השימוש ב blink). אינני מתחייב שהרשימה מלאה ו/או מעודכנת.