חקירה מעמיקה של שיתוף משאבים חוצה-מקורות (CORS) ובקשות preflight. למדו כיצד לטפל בבעיות CORS ולאבטח את יישומי הווב שלכם עבור קהל גלובלי.
פיצוח CORS: צלילה עמוקה לטיפול בבקשות Preflight ב-JavaScript
בעולם פיתוח הווב ההולך ומתרחב, אבטחה היא ערך עליון. שיתוף משאבים חוצה-מקורות (CORS) הוא מנגנון אבטחה חיוני המיושם על ידי דפדפני אינטרנט כדי להגביל דפי אינטרנט מלבצע בקשות לדומיין שונה מזה שהגיש את הדף. זהו מאפיין אבטחה בסיסי שנועד למנוע מאתרים זדוניים לגשת לנתונים רגישים. מדריך מקיף זה יצלול לנבכי ה-CORS, תוך התמקדות ספציפית בטיפול בבקשות preflight. נחקור את ה'למה', ה'מה' וה'איך' של CORS, ונספק דוגמאות מעשיות ופתרונות לבעיות נפוצות שמפתחים ברחבי העולם נתקלים בהן.
הבנת מדיניות אותו המקור (Same-Origin Policy)
בליבת ה-CORS נמצאת מדיניות אותו המקור (SOP). מדיניות זו היא מנגנון אבטחה ברמת הדפדפן המגביל סקריפטים הפועלים ממקור אחד מלגשת למשאבים ממקור אחר. מקור מוגדר על ידי הפרוטוקול (למשל, HTTP או HTTPS), הדומיין (למשל, example.com) והפורט (למשל, 80 או 443). לשתי כתובות URL יש את אותו המקור אם שלושת המרכיבים הללו תואמים בדיוק.
לדוגמה:
https://www.example.com/app1/index.html
ו-https://www.example.com/app2/index.html
הן מאותו מקור (אותו פרוטוקול, דומיין ופורט).https://www.example.com/index.html
ו-http://www.example.com/index.html
הן ממקורות שונים (פרוטוקולים שונים).https://www.example.com/index.html
ו-https://api.example.com/index.html
הן ממקורות שונים (תתי-דומיינים שונים נחשבים לדומיינים שונים).https://www.example.com:8080/index.html
ו-https://www.example.com/index.html
הן ממקורות שונים (פורטים שונים).
מדיניות ה-SOP נועדה למנוע מסקריפטים זדוניים באתר אחד לגשת לנתונים רגישים, כמו קובצי Cookie או מידע אימות משתמש, באתר אחר. בעוד שהיא חיונית לאבטחה, ה-SOP יכולה להיות גם מגבילה, במיוחד כאשר יש צורך בבקשות לגיטימיות חוצות-מקורות.
מהו שיתוף משאבים חוצה-מקורות (CORS)?
CORS הוא מנגנון המאפשר לשרתים לציין אילו מקורות (דומיינים, סכמות או פורטים) רשאים לגשת למשאבים שלהם. הוא למעשה מקל על מדיניות ה-SOP, ומאפשר גישה מבוקרת חוצת-מקורות. CORS מיושם באמצעות כותרות HTTP המוחלפות בין הלקוח (בדרך כלל דפדפן אינטרנט) לבין השרת.
כאשר דפדפן מבצע בקשה חוצת-מקורות (כלומר, בקשה למקור שונה מהדף הנוכחי), הוא בודק תחילה אם השרת מאשר את הבקשה. הדבר נעשה על ידי בחינת הכותרת Access-Control-Allow-Origin
בתגובת השרת. אם מקור הבקשה מופיע בכותרת זו (או אם הכותרת מוגדרת ל-*
, המאפשרת לכל המקורות), הדפדפן מאפשר לבקשה להמשיך. אחרת, הדפדפן חוסם את הבקשה, ומונע מקוד ה-JavaScript לגשת לנתוני התגובה.
תפקידן של בקשות Preflight
עבור סוגים מסוימים של בקשות חוצות-מקורות, הדפדפן יוזם בקשת preflight. זוהי בקשת OPTIONS
הנשלחת לשרת לפני הבקשה הממשית. מטרת בקשת ה-preflight היא לקבוע אם השרת מוכן לקבל את הבקשה הממשית. השרת מגיב לבקשת ה-preflight עם מידע על המתודות, הכותרות וההגבלות האחרות המותרות.
בקשות Preflight מופעלות כאשר הבקשה חוצת-המקורות עומדת באחד מהתנאים הבאים:
- מתודת הבקשה אינה
GET
,HEAD
, אוPOST
. - הבקשה כוללת כותרות מותאמות אישית (כלומר, כותרות שאינן אלו המתווספות אוטומטית על ידי הדפדפן).
- כותרת ה-
Content-Type
מוגדרת לכל דבר שאינוapplication/x-www-form-urlencoded
,multipart/form-data
, אוtext/plain
. - הבקשה משתמשת באובייקטים מסוג
ReadableStream
בגוף הבקשה.
לדוגמה, בקשת PUT
עם Content-Type
של application/json
תפעיל בקשת preflight מכיוון שהיא משתמשת במתודה שונה מהמתודות המותרות ובסוג תוכן שעלול להיות לא מורשה.
מדוע בקשות Preflight?
בקשות Preflight חיוניות לאבטחה מכיוון שהן מספקות לשרת הזדמנות לדחות בקשות חוצות-מקורות שעלולות להיות מזיקות לפני שהן מבוצעות. ללא בקשות preflight, אתר זדוני עלול לשלוח בקשות שרירותיות לשרת ללא הסכמתו המפורשת של השרת. בקשת preflight מאפשרת לשרת לוודא שהבקשה מקובלת ומונעת פעולות שעלולות להזיק.
טיפול בבקשות Preflight בצד השרת
טיפול נכון בבקשות preflight הוא חיוני כדי להבטיח שהיישום שלכם פועל כראוי ובאופן מאובטח. על השרת להגיב לבקשת ה-OPTIONS
עם כותרות ה-CORS המתאימות כדי לציין אם הבקשה הממשית מותרת.
להלן פירוט של כותרות ה-CORS המרכזיות המשמשות בתגובות preflight:
Access-Control-Allow-Origin
: כותרת זו מציינת את המקור/ות הרשאים לגשת למשאב. ניתן להגדיר אותה למקור ספציפי (למשל,https://www.example.com
) או ל-*
כדי לאפשר לכל המקורות. עם זאת, שימוש ב-*
בדרך כלל אינו מומלץ מטעמי אבטחה, במיוחד אם השרת מטפל בנתונים רגישים.Access-Control-Allow-Methods
: כותרת זו מציינת את מתודות ה-HTTP המותרות עבור הבקשה חוצת-המקורות (למשל,GET
,POST
,PUT
,DELETE
).Access-Control-Allow-Headers
: כותרת זו מציינת את רשימת כותרות ה-HTTP הלא-סטנדרטיות המותרות בבקשה הממשית. הדבר נחוץ אם הלקוח שולח כותרות מותאמות אישית, כגוןX-Custom-Header
אוAuthorization
.Access-Control-Allow-Credentials
: כותרת זו מציינת אם הבקשה הממשית יכולה לכלול פרטי הזדהות, כגון קובצי Cookie או כותרות הרשאה. יש להגדיר אותה ל-true
אם קוד צד-הלקוח שולח פרטי הזדהות והשרת אמור לקבל אותם. שימו לב: כאשר כותרת זו מוגדרת ל-`true`, *לא ניתן* להגדיר את `Access-Control-Allow-Origin` ל-`*`. יש לציין את המקור.Access-Control-Max-Age
: כותרת זו מציינת את משך הזמן המרבי (בשניות) שהדפדפן יכול לשמור במטמון את תגובת ה-preflight. הדבר יכול לעזור בשיפור הביצועים על ידי הפחתת מספר בקשות ה-preflight הנשלחות.
דוגמה: טיפול בבקשות Preflight ב-Node.js עם Express
להלן דוגמה לאופן הטיפול בבקשות preflight ביישום Node.js באמצעות פריימוורק Express:
const express = require('express');
const cors = require('cors');
const app = express();
// הפעלת CORS לכל המקורות (למטרות פיתוח בלבד!)
// בסביבת ייצור, ציינו מקורות מורשים לאבטחה טובה יותר.
app.use(cors()); //or app.use(cors({origin: 'https://www.example.com'}));
// נתיב לטיפול בבקשות OPTIONS (preflight)
app.options('/data', cors()); // הפעלת CORS לנתיב יחיד. או ציון מקור: cors({origin: 'https://www.example.com'})
// נתיב לטיפול בבקשות GET
app.get('/data', (req, res) => {
res.json({ message: 'This is cross-origin data!' });
});
// נתיב לטיפול ב-preflight ובבקשת post
app.options('/resource', cors()); // אפשור בקשת pre-flight עבור בקשת DELETE
app.delete('/resource', cors(), (req, res, next) => {
res.send('delete resource')
})
const port = 3000;
app.listen(port, () => {
console.log(`Server listening on port ${port}`);
});
בדוגמה זו, אנו משתמשים ב-middleware cors
כדי לטפל בבקשות CORS. לשליטה פרטנית יותר, ניתן להפעיל CORS על בסיס נתיב בודד. שימו לב: בסביבת ייצור, מומלץ בחום לציין את המקורות המותרים באמצעות האפשרות origin
במקום לאפשר לכל המקורות. מתן הרשאה לכל המקורות באמצעות *
עלול לחשוף את היישום שלכם לפגיעויות אבטחה.
דוגמה: טיפול בבקשות Preflight בפייתון עם Flask
להלן דוגמה לאופן הטיפול בבקשות preflight ביישום פייתון באמצעות פריימוורק Flask והרחבת flask_cors
:
from flask import Flask, jsonify
from flask_cors import CORS, cross_origin
app = Flask(__name__)
CORS(app) # הפעלת CORS לכל הנתיבים
@app.route('/data')
@cross_origin()
def get_data():
data = {"message": "This is cross-origin data!"}
return jsonify(data)
if __name__ == '__main__':
app.run(debug=True)
זהו השימוש הפשוט ביותר. כמו קודם, ניתן להגביל את המקורות. עיינו בתיעוד של flask-cors לפרטים נוספים.
דוגמה: טיפול בבקשות Preflight ב-Java עם Spring Boot
להלן דוגמה לאופן הטיפול בבקשות preflight ביישום Java באמצעות Spring Boot:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@SpringBootApplication
public class CorsApplication {
public static void main(String[] args) {
SpringApplication.run(CorsApplication.class, args);
}
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/data").allowedOrigins("http://localhost:8080");
}
};
}
}
והבקר (controller) המתאים:
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class DataController {
@GetMapping("/data")
public String getData() {
return "This is cross-origin data!";
}
}
בעיות CORS נפוצות ופתרונותיהן
למרות חשיבותו, CORS יכול לעיתים קרובות להיות מקור לתסכול עבור מפתחים. הנה כמה בעיות CORS נפוצות והפתרונות שלהן:
-
שגיאה: "No 'Access-Control-Allow-Origin' header is present on the requested resource."
שגיאה זו מצביעה על כך שהשרת אינו מחזיר את כותרת
Access-Control-Allow-Origin
בתגובתו. כדי לתקן זאת, ודאו שהשרת מוגדר לכלול את הכותרת ושהיא מוגדרת למקור הנכון או ל-*
(במידת הצורך).פתרון: הגדירו את השרת כך שיכלול את כותרת `Access-Control-Allow-Origin` בתגובתו, והגדירו אותה למקור של האתר המבקש או ל-`*` כדי לאפשר לכל המקורות (היזהרו בשימוש זה).
-
שגיאה: "Response to preflight request doesn't pass access control check: Request header field X-Custom-Header is not allowed by Access-Control-Allow-Headers in preflight response."
שגיאה זו מצביעה על כך שהשרת אינו מאפשר את הכותרת המותאמת אישית (
X-Custom-Header
בדוגמה זו) בבקשה חוצת-המקורות. כדי לתקן זאת, ודאו שהשרת כולל את הכותרת בכותרתAccess-Control-Allow-Headers
בתגובת ה-preflight.פתרון: הוסיפו את הכותרת המותאמת אישית (למשל, `X-Custom-Header`) לכותרת `Access-Control-Allow-Headers` בתגובת ה-preflight של השרת.
-
שגיאה: "Credentials flag is 'true', but the 'Access-Control-Allow-Origin' header is '*'."
כאשר כותרת ה-
Access-Control-Allow-Credentials
מוגדרת ל-true
, כותרת ה-Access-Control-Allow-Origin
חייבת להיות מוגדרת למקור ספציפי, ולא ל-*
. הסיבה לכך היא שהתרת פרטי הזדהות מכל המקורות תהווה סיכון אבטחה.פתרון: בעת שימוש בפרטי הזדהות, הגדירו את `Access-Control-Allow-Origin` למקור ספציפי במקום ל-`*`.
-
בקשת ה-Preflight אינה נשלחת.
בדקו היטב שקוד ה-Javascript שלכם כולל את המאפיין `credentials: 'include'`. בדקו גם שהשרת שלכם מאפשר `Access-Control-Allow-Credentials: true`.
-
תצורות סותרות בין השרת ללקוח.
בדקו בקפידה את תצורת ה-CORS בצד השרת לצד ההגדרות בצד הלקוח. חוסר התאמה (למשל, שרת המאפשר רק בקשות GET אך הלקוח שולח POST) יגרום לשגיאות CORS.
CORS ושיטות עבודה מומלצות לאבטחה
בעוד ש-CORS מאפשר גישה מבוקרת חוצת-מקורות, חיוני להקפיד על שיטות עבודה מומלצות לאבטחה כדי למנוע פגיעויות:
- הימנעו משימוש ב-
*
בכותרתAccess-Control-Allow-Origin
בסביבת ייצור. הדבר מאפשר לכל המקורות לגשת למשאבים שלכם, מה שעלול להוות סיכון אבטחה. במקום זאת, ציינו את המקורות המדויקים המותרים. - שקלו היטב אילו מתודות וכותרות לאפשר. אפשרו רק את המתודות והכותרות ההכרחיות לתפקודו התקין של היישום שלכם.
- יישמו מנגנוני אימות והרשאה נאותים. CORS אינו תחליף לאימות והרשאה. ודאו שה-API שלכם מוגן באמצעי אבטחה מתאימים.
- אמתו וחטאו (sanitize) את כל קלט המשתמש. הדבר מסייע במניעת התקפות Cross-Site Scripting (XSS) ופגיעויות אחרות.
- שמרו על תצורת ה-CORS בצד השרת מעודכנת. בדקו ועדכנו באופן קבוע את תצורת ה-CORS שלכם כדי להבטיח שהיא תואמת לדרישות האבטחה של היישום שלכם.
CORS בסביבות פיתוח שונות
בעיות CORS יכולות להתבטא באופן שונה בסביבות פיתוח וטכנולוגיות שונות. הנה מבט על איך לגשת ל-CORS בכמה תרחישים נפוצים:
סביבות פיתוח מקומיות
במהלך פיתוח מקומי, בעיות CORS יכולות להיות מעצבנות במיוחד. דפדפנים חוסמים לעתים קרובות בקשות משרת הפיתוח המקומי שלכם (למשל, localhost:3000
) ל-API מרוחק. מספר טכניקות יכולות להקל על כאב זה:
- תוספים לדפדפן: תוספים כמו "Allow CORS: Access-Control-Allow-Origin" יכולים להשבית זמנית את הגבלות ה-CORS למטרות בדיקה. עם זאת, *לעולם* אל תשתמשו בהם בסביבת ייצור.
- שרתי פרוקסי: הגדירו שרת פרוקסי המעביר בקשות משרת הפיתוח המקומי שלכם ל-API המרוחק. זה למעשה הופך את הבקשות ל"מאותו מקור" מנקודת מבטו של הדפדפן. כלים כמו
http-proxy-middleware
(עבור Node.js) מועילים לכך. - הגדרת CORS בשרת: גם במהלך הפיתוח, מומלץ להגדיר את שרת ה-API שלכם כך שיאפשר במפורש בקשות ממקור הפיתוח המקומי שלכם (למשל,
http://localhost:3000
). זה מדמה תצורת CORS בעולם האמיתי ועוזר לכם לתפוס בעיות מוקדם.
סביבות Serverless (למשל, AWS Lambda, Google Cloud Functions)
פונקציות Serverless דורשות לעתים קרובות תצורת CORS קפדנית. פלטפורמות serverless רבות מספקות תמיכה מובנית ב-CORS, אך חיוני להגדיר אותה נכון:
- הגדרות ספציפיות לפלטפורמה: השתמשו באפשרויות תצורת ה-CORS המובנות של הפלטפורמה. AWS Lambda, למשל, מאפשרת לכם לציין מקורות, מתודות וכותרות מותרות ישירות בהגדרות ה-API Gateway.
- Middleware/ספריות: לגמישות רבה יותר, תוכלו להשתמש ב-middleware או בספריות כדי לטפל ב-CORS בתוך קוד פונקציית ה-serverless שלכם. זה דומה לגישות המשמשות בסביבות שרת מסורתיות (למשל, שימוש בחבילת `cors` בפונקציות Lambda ב-Node.js).
- שקלו את מתודת `OPTIONS`: ודאו שפונקציית ה-serverless שלכם מטפלת כראוי בבקשות
OPTIONS
. זה כרוך לעתים קרובות ביצירת נתיב נפרד שמחזיר את כותרות ה-CORS המתאימות.
פיתוח אפליקציות מובייל (למשל, React Native, Flutter)
CORS מהווה פחות דאגה ישירה עבור אפליקציות מובייל נייטיב (אנדרואיד, iOS), מכיוון שהן בדרך כלל אינן אוכפות את מדיניות אותו המקור באותו אופן כמו דפדפני אינטרנט. עם זאת, CORS עדיין יכול להיות רלוונטי אם אפליקציית המובייל שלכם משתמשת ב-web view כדי להציג תוכן אינטרנטי או אם אתם משתמשים בפריימוורקים כמו React Native או Flutter הממנפים JavaScript:
- Web Views: אם אפליקציית המובייל שלכם משתמשת ב-web view להצגת תוכן אינטרנטי, חלים אותם כללי CORS כמו בדפדפן אינטרנט. הגדירו את השרת שלכם כדי לאפשר בקשות ממקור תוכן האינטרנט.
- React Native/Flutter: פריימוורקים אלה משתמשים ב-JavaScript לביצוע בקשות API. בעוד שהסביבה הנייטיב עשויה שלא לאכוף CORS ישירות, לקוחות ה-HTTP הבסיסיים (למשל,
fetch
) עשויים עדיין להפגין התנהגות דמוית CORS במצבים מסוימים. - לקוחות HTTP נייטיב: בעת ביצוע בקשות API ישירות מקוד נייטיב (למשל, שימוש ב-OkHttp באנדרואיד או URLSession ב-iOS), CORS בדרך כלל אינו גורם. עם זאת, עדיין עליכם לשקול שיטות עבודה מומלצות לאבטחה כגון אימות והרשאה נאותים.
שיקולים גלובליים לתצורת CORS
בעת הגדרת CORS עבור יישום נגיש גלובלית, חיוני לשקול גורמים כגון:
- ריבונות נתונים: תקנות באזורים מסוימים מחייבות שנתונים יישארו בתוך האזור. CORS עשוי להיות מעורב בעת גישה למשאבים מעבר לגבולות, מה שעלול להתנגש עם חוקי תושבות נתונים.
- מדיניות אבטחה אזורית: למדינות שונות עשויות להיות תקנות והנחיות סייבר שונות המשפיעות על אופן יישום ואבטחת CORS.
- רשתות להפצת תוכן (CDNs): ודאו שה-CDN שלכם מוגדר כראוי להעביר את כותרות ה-CORS הדרושות. CDNs שהוגדרו באופן שגוי יכולים להסיר כותרות CORS, מה שמוביל לשגיאות בלתי צפויות.
- מאזני עומסים ופרוקסי: ודאו שכל מאזני העומסים או הפרוקסי ההפוכים בתשתית שלכם מטפלים כראוי בבקשות preflight ומעבירים את כותרות ה-CORS.
- תמיכה רב-לשונית: שקלו כיצד CORS מתקשר עם אסטרטגיות הבינאום (i18n) והלוקליזציה (l10n) של היישום שלכם. ודאו שמדיניות ה-CORS עקבית בין גרסאות שפה שונות של היישום שלכם.
בדיקה וניפוי שגיאות ב-CORS
בדיקה וניפוי שגיאות יעילים ב-CORS הם חיוניים. הנה כמה טכניקות:
- כלי מפתחים בדפדפן: קונסולת המפתחים של הדפדפן היא התחנה הראשונה שלכם. לשונית "Network" תציג בקשות preflight ותגובות, ותחשוף אם כותרות CORS קיימות ומוגדרות כראוי.
- כלי שורת הפקודה `curl`: השתמשו ב-`curl -v -X OPTIONS
` כדי לשלוח ידנית בקשות preflight ולבחון את כותרות התגובה של השרת. - בודקי CORS מקוונים: כלים מקוונים רבים יכולים לעזור לאמת את תצורת ה-CORS שלכם. פשוט חפשו "CORS checker".
- בדיקות יחידה ואינטגרציה: כתבו בדיקות אוטומטיות כדי לוודא שתצורת ה-CORS שלכם פועלת כמצופה. בדיקות אלו צריכות לכסות הן בקשות חוצות-מקורות מוצלחות והן תרחישים שבהם CORS אמור לחסום גישה.
- רישום וניטור: יישמו רישום (logging) למעקב אחר אירועים הקשורים ל-CORS, כגון בקשות preflight ובקשות חסומות. נטרו את הלוגים שלכם לפעילות חשודה או לשגיאות תצורה.
סיכום
שיתוף משאבים חוצה-מקורות (CORS) הוא מנגנון אבטחה חיוני המאפשר גישה מבוקרת חוצת-מקורות למשאבי אינטרנט. הבנת אופן הפעולה של CORS, במיוחד בקשות preflight, חיונית לבניית יישומי ווב מאובטחים ואמינים. על ידי הקפדה על שיטות העבודה המומלצות המפורטות במדריך זה, תוכלו לטפל ביעילות בבעיות CORS ולהגן על היישום שלכם מפני פגיעויות פוטנציאליות. זכרו תמיד לתעדף אבטחה ולשקול היטב את ההשלכות של תצורת ה-CORS שלכם.
ככל שפיתוח הווב מתפתח, CORS ימשיך להיות היבט קריטי באבטחת הרשת. הישארות מעודכנת לגבי שיטות העבודה והטכניקות העדכניות ביותר של CORS חיונית לבניית יישומי ווב מאובטחים ונגישים גלובלית.