מדריך מקיף להבנה ויישום של Cross-Origin Resource Sharing (CORS) לתקשורת JavaScript מאובטחת בין דומיינים שונים.
יישום אבטחה בין-מקורות (Cross-Origin): שיטות עבודה מומלצות לתקשורת JavaScript
בעולם הרשת המקושר של היום, יישומי JavaScript צריכים לעיתים קרובות לתקשר עם משאבים ממקורות שונים (דומיינים, פרוטוקולים או פורטים). אינטראקציה זו נשלטת על ידי מדיניות אותו המקור (Same-Origin Policy) של הדפדפן, מנגנון אבטחה חיוני שנועד למנוע מסקריפטים זדוניים לגשת לנתונים רגישים מעבר לגבולות הדומיין. עם זאת, תקשורת לגיטימית בין מקורות שונים היא לעיתים קרובות הכרחית. כאן נכנס לתמונה מנגנון שיתוף המשאבים חוצה-המקורות (Cross-Origin Resource Sharing - CORS). מאמר זה מספק סקירה מקיפה של CORS, יישומו ושיטות עבודה מומלצות לתקשורת מאובטחת בין מקורות שונים ב-JavaScript.
הבנת מדיניות אותו המקור (Same-Origin Policy)
מדיניות אותו המקור (SOP) היא מושג אבטחה בסיסי בדפדפני אינטרנט. היא מגבילה סקריפטים הרצים על מקור אחד מגישה למשאבים ממקור אחר. מקור מוגדר על ידי שילוב של הפרוטוקול (לדוגמה, HTTP או HTTPS), שם הדומיין (לדוגמה, example.com), ומספר הפורט (לדוגמה, 80 או 443). שתי כתובות URL הן בעלות אותו מקור רק אם כל שלושת המרכיבים הללו תואמים במדויק.
לדוגמה:
http://www.example.comו-http://www.example.com/path: אותו מקורhttp://www.example.comו-https://www.example.com: מקור שונה (פרוטוקול שונה)http://www.example.comו-http://subdomain.example.com: מקור שונה (דומיין שונה)http://www.example.com:80ו-http://www.example.com:8080: מקור שונה (פורט שונה)
מדיניות ה-SOP מהווה הגנה קריטית מפני התקפות Cross-Site Scripting (XSS), שבהן סקריפטים זדוניים המוזרקים לאתר אינטרנט יכולים לגנוב נתוני משתמש או לבצע פעולות לא מורשות בשם המשתמש.
מהו שיתוף משאבים חוצה-מקורות (CORS)?
CORS הוא מנגנון המשתמש בכותרות HTTP כדי לאפשר לשרתים לציין אילו מקורות (דומיינים, סכמות או פורטים) מורשים לגשת למשאבים שלהם. הוא למעשה מקל על מדיניות אותו המקור עבור בקשות ספציפיות בין מקורות, ומאפשר תקשורת לגיטימית תוך הגנה מפני התקפות זדוניות.
CORS פועל על ידי הוספת כותרות HTTP חדשות המציינות את המקורות המותרים ואת המתודות (לדוגמה, GET, POST, PUT, DELETE) המותרות לבקשות בין-מקורות. כאשר דפדפן מבצע בקשה בין-מקורות, הוא שולח כותרת Origin עם הבקשה. השרת מגיב עם כותרת Access-Control-Allow-Origin המציינת את המקור(ות) המותר(ים). אם מקור הבקשה תואם לערך בכותרת Access-Control-Allow-Origin (או אם הערך הוא *), הדפדפן מאפשר לקוד ה-JavaScript לגשת לתגובה.
כיצד CORS עובד: הסבר מפורט
תהליך ה-CORS כולל בדרך כלל שני סוגי בקשות:
- בקשות פשוטות (Simple Requests): אלו הן בקשות העומדות בקריטריונים ספציפיים. אם בקשה עומדת בתנאים אלה, הדפדפן שולח את הבקשה ישירות.
- בקשות עם בדיקה מקדימה (Preflighted Requests): אלו הן בקשות מורכבות יותר הדורשות מהדפדפן לשלוח תחילה בקשת OPTIONS "מקדימה" (preflight) לשרת כדי לקבוע אם הבקשה האמיתית בטוחה לשליחה.
1. בקשות פשוטות
בקשה נחשבת "פשוטה" אם היא עומדת בכל התנאים הבאים:
- המתודה היא
GET,HEAD, אוPOST. - אם המתודה היא
POST, כותרת ה-Content-Typeהיא אחת מהבאות: application/x-www-form-urlencodedmultipart/form-datatext/plain- לא הוגדרו כותרות מותאמות אישית.
דוגמה לבקשה פשוטה:
GET /resource HTTP/1.1
Origin: http://www.example.com
דוגמה לתגובת שרת המאשרת את המקור:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://www.example.com
Content-Type: application/json
{
"data": "Some data"
}
אם כותרת Access-Control-Allow-Origin קיימת וערכה תואם למקור הבקשה או מוגדר כ-*, הדפדפן מאפשר לסקריפט לגשת לנתוני התגובה. אחרת, הדפדפן חוסם את הגישה לתגובה, והודעת שגיאה מוצגת במסוף.
2. בקשות עם בדיקה מקדימה (Preflighted Requests)
בקשה נחשבת "עם בדיקה מקדימה" אם היא אינה עומדת בקריטריונים של בקשה פשוטה. זה קורה בדרך כלל כאשר הבקשה משתמשת במתודת HTTP שונה (לדוגמה, PUT, DELETE), מגדירה כותרות מותאמות אישית, או משתמשת ב-Content-Type שאינו אחד מהערכים המותרים.
לפני שליחת הבקשה האמיתית, הדפדפן שולח תחילה בקשת OPTIONS לשרת. בקשת ה-"preflight" הזו כוללת את הכותרות הבאות:
Origin: מקור הדף המבקש.Access-Control-Request-Method: מתודת ה-HTTP שתשמש בבקשה האמיתית (לדוגמה,PUT,DELETE).Access-Control-Request-Headers: רשימה מופרדת בפסיקים של הכותרות המותאמות אישית שיישלחו בבקשה האמיתית.
דוגמה לבקשת preflight:
OPTIONS /resource HTTP/1.1
Origin: http://www.example.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header, Content-Type
השרת חייב להגיב לבקשת ה-OPTIONS עם הכותרות הבאות:
Access-Control-Allow-Origin: המקור המורשה לבצע את הבקשה (או*כדי לאפשר כל מקור).Access-Control-Allow-Methods: רשימה מופרדת בפסיקים של מתודות HTTP המותרות לבקשות בין-מקורות (לדוגמה,GET,POST,PUT,DELETE).Access-Control-Allow-Headers: רשימה מופרדת בפסיקים של הכותרות המותאמות אישית המותרות לשליחה בבקשה.Access-Control-Max-Age: מספר השניות שתגובת ה-preflight יכולה להישמר במטמון (cache) של הדפדפן.
דוגמה לתגובת שרת לבקשת preflight:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://www.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: X-Custom-Header, Content-Type
Access-Control-Max-Age: 86400
אם תגובת השרת לבקשת ה-preflight מציינת שהבקשה האמיתית מותרת, הדפדפן ישלח את הבקשה האמיתית. אחרת, הדפדפן יחסום את הבקשה ויציג הודעת שגיאה.
יישום CORS בצד השרת
CORS מיושם בעיקר בצד השרת על ידי הגדרת כותרות ה-HTTP המתאימות בתגובה. פרטי היישום הספציפיים ישתנו בהתאם לטכנולוגיית צד-השרת שבה נעשה שימוש.
דוגמה באמצעות Node.js עם Express:
const express = require('express');
const cors = require('cors');
const app = express();
// Enable CORS for all origins
app.use(cors());
// Alternatively, configure CORS for specific origins
// const corsOptions = {
// origin: 'http://www.example.com'
// };
// app.use(cors(corsOptions));
app.get('/resource', (req, res) => {
res.json({ message: 'This is a CORS-enabled resource' });
});
app.listen(3000, () => {
console.log('Server listening on port 3000');
});
חבילת ה-middleware של cors מפשטת את תהליך הגדרת כותרות CORS ב-Express. ניתן לאפשר CORS לכל המקורות באמצעות cors() או להגדיר אותו למקורות ספציפיים באמצעות cors(corsOptions).
דוגמה באמצעות Python עם Flask:
from flask import Flask
from flask_cors import CORS
app = Flask(__name__)
CORS(app)
@app.route("/resource")
def hello():
return {"message": "This is a CORS-enabled resource"}
if __name__ == '__main__':
app.run(debug=True)
הרחבת flask_cors מספקת דרך פשוטה לאפשר CORS ביישומי Flask. ניתן לאפשר CORS לכל המקורות על ידי העברת app ל-CORS(). גם הגדרה למקורות ספציפיים אפשרית.
דוגמה באמצעות Java עם Spring Boot:
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/resource")
.allowedOrigins("http://www.example.com")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowedHeaders("Content-Type", "X-Custom-Header")
.allowCredentials(true)
.maxAge(3600);
}
}
ב-Spring Boot, ניתן להגדיר CORS באמצעות WebMvcConfigurer. זה מאפשר שליטה דקדקנית על המקורות המורשים, המתודות, הכותרות והגדרות CORS אחרות.
הגדרת כותרות CORS ישירות (דוגמה כללית)
אם אינכם משתמשים ב-framework כלשהו, תוכלו להגדיר כותרות ישירות בקוד צד השרת שלכם (לדוגמה, PHP, Ruby on Rails וכו'):
שיטות עבודה מומלצות ל-CORS
כדי להבטיח תקשורת בין-מקורות מאובטחת ויעילה, עקבו אחר השיטות המומלצות הבאות:
- הימנעו משימוש ב-
Access-Control-Allow-Origin: *בסביבת ייצור (Production): התרת גישה למשאבים שלכם מכל המקורות עלולה להוות סיכון אבטחה. במקום זאת, ציינו במפורש את המקורות המותרים. - השתמשו ב-HTTPS: השתמשו תמיד ב-HTTPS הן עבור המקור המבקש והן עבור המקור המגיש כדי להגן על נתונים במעבר.
- אמתו קלט: אמתו ובצעו סניטציה לנתונים המתקבלים מבקשות בין-מקורות כדי למנוע התקפות הזרקה (injection attacks).
- ישמו אימות והרשאה נאותים: ודאו שרק משתמשים מורשים יכולים לגשת למשאבים רגישים.
- שמרו במטמון תגובות preflight: השתמשו ב-
Access-Control-Max-Ageכדי לשמור תגובות preflight במטמון ולהפחית את מספר בקשות ה-OPTIONS. - שקלו שימוש באימות (Credentials): אם ה-API שלכם דורש אימות באמצעות קובצי Cookie או אימות HTTP, עליכם להגדיר את הכותרת
Access-Control-Allow-Credentialsל-trueבשרת ואת האפשרותcredentialsל-'include'בקוד ה-JavaScript שלכם (לדוגמה, בעת שימוש ב-fetchאוXMLHttpRequest). היו זהירים במיוחד בעת שימוש באפשרות זו, מכיוון שהיא עלולה להכניס פרצות אבטחה אם לא מטופלת כראוי. כמו כן, כאשר Access-Control-Allow-Credentials מוגדר ל-true, לא ניתן להגדיר את Access-Control-Allow-Origin ל-"*". עליכם לציין במפורש את המקור(ות) המותר(ים). - סקרו ועדכנו את תצורת ה-CORS באופן קבוע: ככל שהיישום שלכם מתפתח, סקרו ועדכנו באופן קבוע את תצורת ה-CORS כדי להבטיח שהיא נשארת מאובטחת ועונה על צרכיכם.
- הבינו את ההשלכות של תצורות CORS שונות: היו מודעים להשלכות האבטחה של תצורות CORS שונות ובחרו את התצורה המתאימה ליישום שלכם.
- בדקו את יישום ה-CORS שלכם: בדקו ביסודיות את יישום ה-CORS שלכם כדי לוודא שהוא פועל כמצופה ואינו מציג פרצות אבטחה חדשות. השתמשו בכלי המפתחים של הדפדפן כדי לבדוק בקשות ותגובות רשת, והשתמשו בכלי בדיקה אוטומטיים כדי לאמת את התנהגות ה-CORS.
דוגמה: שימוש ב-Fetch API עם CORS
הנה דוגמה לשימוש ב-fetch API לביצוע בקשה בין-מקורות:
fetch('https://api.example.com/data', {
method: 'GET',
mode: 'cors', // Tells the browser this is a CORS request
headers: {
'Content-Type': 'application/json',
'X-Custom-Header': 'value'
}
})
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
console.log(data);
})
.catch(error => {
console.error('There was a problem with the fetch operation:', error);
});
האפשרות mode: 'cors' אומרת לדפדפן שזו בקשת CORS. אם השרת אינו מאפשר את המקור, הדפדפן יחסום את הגישה לתגובה, ותיזרק שגיאה.
אם אתם משתמשים באימות (לדוגמה, קובצי Cookie), עליכם להגדיר את האפשרות credentials ל-'include':
fetch('https://api.example.com/data', {
method: 'GET',
mode: 'cors',
credentials: 'include', // Include cookies in the request
headers: {
'Content-Type': 'application/json'
}
})
.then(response => {
// ...
});
CORS ו-JSONP
JSON with Padding (JSONP) היא טכניקה ישנה יותר לעקיפת מדיניות אותו המקור. היא פועלת על ידי יצירה דינמית של תג <script> הטוען נתונים מדומיין אחר. בעוד ש-JSONP יכול להיות שימושי במצבים מסוימים, יש לו מגבלות אבטחה משמעותיות ויש להימנע ממנו במידת האפשר. CORS הוא הפתרון המועדף לתקשורת בין-מקורות מכיוון שהוא מספק מנגנון מאובטח וגמיש יותר.
הבדלים עיקריים בין CORS ל-JSONP:
- אבטחה: CORS מאובטח יותר מ-JSONP מכיוון שהוא מאפשר לשרת לשלוט אילו מקורות מורשים לגשת למשאבים שלו. JSONP אינו מספק כל בקרת מקור.
- מתודות HTTP: CORS תומך בכל מתודות ה-HTTP (לדוגמה,
GET,POST,PUT,DELETE), בעוד ש-JSONP תומך רק בבקשותGET. - טיפול בשגיאות: CORS מספק טיפול בשגיאות טוב יותר מ-JSONP. כאשר בקשת CORS נכשלת, הדפדפן מספק הודעות שגיאה מפורטות. טיפול בשגיאות ב-JSONP מוגבל לזיהוי אם הסקריפט נטען בהצלחה.
פתרון בעיות CORS
בעיות CORS יכולות להיות מתסכלות לניפוי באגים. הנה כמה טיפים נפוצים לפתרון בעיות:
- בדקו את מסוף הדפדפן: מסוף הדפדפן בדרך כלל יספק הודעות שגיאה מפורטות על בעיות CORS.
- בדקו בקשות רשת: השתמשו בכלי המפתחים של הדפדפן כדי לבדוק את כותרות ה-HTTP של הבקשה והתגובה. ודאו שהכותרות
Originו-Access-Control-Allow-Originמוגדרות כהלכה. - אמתו את תצורת צד-השרת: בדקו שוב את תצורת ה-CORS בצד השרת כדי לוודא שהיא מאפשרת את המקורות, המתודות והכותרות הנכונות.
- נקו את מטמון הדפדפן: לפעמים, תגובות preflight שנשמרו במטמון יכולות לגרום לבעיות CORS. נסו לנקות את מטמון הדפדפן או להשתמש בחלון גלישה פרטי.
- השתמשו ב-Proxy של CORS: במקרים מסוימים, ייתכן שתצטרכו להשתמש ב-proxy של CORS כדי לעקוף הגבלות CORS. עם זאת, היו מודעים לכך ששימוש ב-proxy כזה עלול להכניס סיכוני אבטחה.
- חפשו תצורות שגויות: חפשו תצורות שגויות נפוצות כגון כותרת
Access-Control-Allow-Originחסרה, ערכים שגויים ב-Access-Control-Allow-MethodsאוAccess-Control-Allow-Headers, או כותרתOriginשגויה בבקשה.
סיכום
שיתוף משאבים חוצה-מקורות (CORS) הוא מנגנון חיוני המאפשר תקשורת מאובטחת בין מקורות שונים ביישומי JavaScript. על ידי הבנת מדיניות אותו המקור, תהליך העבודה של CORS וכותרות ה-HTTP השונות המעורבות, מפתחים יכולים ליישם CORS ביעילות כדי להגן על היישומים שלהם מפני פרצות אבטחה תוך מתן אפשרות לבקשות לגיטימיות בין מקורות. הקפדה על שיטות עבודה מומלצות לתצורת CORS וסקירה קבועה של היישום שלכם הן חיוניות לשמירה על יישום רשת מאובטח וחזק.
מדריך מקיף זה מספק בסיס מוצק להבנה ויישום של CORS. זכרו לעיין בתיעוד ובמשאבים הרשמיים של טכנולוגיית צד-השרת הספציפית שלכם כדי להבטיח שאתם מיישמים CORS בצורה נכונה ומאובטחת.