Изчерпателно ръководство за разбиране и прилагане на Cross-Origin Resource Sharing (CORS) за сигурна JavaScript комуникация между различни домейни.
Имплементация на сигурност между различни източници: Най-добри практики за 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): Това са по-сложни заявки, които изискват от браузъра първо да изпрати „предварителна“ (preflight) заявка от тип OPTIONS до сървъра, за да определи дали действителната заявка е безопасна за изпращане.
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. Предварителни заявки
Една заявка се счита за „предварителна“, ако не отговаря на критериите за проста заявка. Това обикновено се случва, когато заявката използва различен HTTP метод (напр. PUT, DELETE), задава персонализирани хедъри или използва Content-Type, различен от разрешените стойности.
Преди да изпрати действителната заявка, браузърът първо изпраща заявка от тип OPTIONS до сървъра. Тази „предварителна“ заявка включва следните хедъри:
Origin: Източникът на изискващата страница.Access-Control-Request-Method: HTTP методът, който ще бъде използван в действителната заявка (напр.PUT,DELETE).Access-Control-Request-Headers: Списък, разделен със запетаи, на персонализираните хедъри, които ще бъдат изпратени в действителната заявка.
Пример за предварителна заявка:
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: Броят секунди, за които предварителният отговор може да бъде кеширан от браузъра.
Пример за отговор на сървъра на предварителна заявка:
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
Ако отговорът на сървъра на предварителната заявка показва, че действителната заявка е разрешена, браузърът ще изпрати действителната заявка. В противен случай браузърът ще блокира заявката и ще покаже съобщение за грешка.
Прилагане на CORS от страна на сървъра
CORS се прилага предимно от страна на сървъра чрез задаване на съответните HTTP хедъри в отговора. Конкретните детайли по прилагането ще варират в зависимост от използваната сървърна технология.
Пример с Node.js и Express:
const express = require('express');
const cors = require('cors');
const app = express();
// Активиране на CORS за всички източници
app.use(cors());
// Алтернативно, конфигуриране на CORS за конкретни източници
// 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');
});
Мидълуерът 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 хедъри (Общ пример)
Ако не използвате никаква рамка, можете да зададете хедъри директно в кода от страна на сървъра (напр. PHP, Ruby on Rails и др.):
Най-добри практики за CORS
За да осигурите сигурна и ефективна комуникация между различни източници, следвайте тези най-добри практики:
- Избягвайте използването на
Access-Control-Allow-Origin: *в производствена среда: Разрешаването на достъп до вашите ресурси от всички източници може да бъде риск за сигурността. Вместо това, посочете точно кои източници са разрешени. - Използвайте HTTPS: Винаги използвайте HTTPS както за източника на заявката, така и за обслужващия източник, за да защитите данните при пренос.
- Валидирайте входа: Винаги валидирайте и почиствайте данните, получени от заявки между различни източници, за да предотвратите атаки от тип инжектиране.
- Приложете правилна автентикация и оторизация: Уверете се, че само оторизирани потребители имат достъп до чувствителни ресурси.
- Кеширайте предварителните отговори: Използвайте
Access-Control-Max-Age, за да кеширате предварителните отговори и да намалите броя наOPTIONSзаявките. - Обмислете използването на данни за удостоверяване (Credentials): Ако вашето API изисква автентикация с бисквитки или 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', // Указва на браузъра, че това е CORS заявка
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 заявка. Ако сървърът не разрешава източника, браузърът ще блокира достъпа до отговора и ще бъде хвърлена грешка.
Ако използвате данни за удостоверяване (напр. бисквитки), трябва да зададете опцията credentials на 'include':
fetch('https://api.example.com/data', {
method: 'GET',
mode: 'cors',
credentials: 'include', // Включване на бисквитки в заявката
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 конфигурация от страна на сървъра, за да се уверите, че тя разрешава правилните източници, методи и хедъри.
- Изчистете кеша на браузъра: Понякога кешираните предварителни отговори могат да причинят проблеми с CORS. Опитайте да изчистите кеша на браузъра си или да използвате прозорец за частно сърфиране.
- Използвайте CORS прокси: В някои случаи може да се наложи да използвате CORS прокси, за да заобиколите CORS ограниченията. Въпреки това, имайте предвид, че използването на CORS прокси може да въведе рискове за сигурността.
- Проверете за грешни конфигурации: Търсете често срещани грешни конфигурации като липсващ хедър
Access-Control-Allow-Origin, неправилни стойности наAccess-Control-Allow-MethodsилиAccess-Control-Allow-Headers, или неправилен хедърOriginв заявката.
Заключение
Споделянето на ресурси между различни източници (CORS) е съществен механизъм за осъществяване на сигурна комуникация между различни източници в JavaScript приложенията. Чрез разбирането на Политиката за същия произход, работния процес на CORS и различните свързани HTTP хедъри, разработчиците могат ефективно да прилагат CORS, за да защитят своите приложения от уязвимости в сигурността, като същевременно позволяват легитимни заявки между различни източници. Следването на най-добрите практики за конфигуриране на CORS и редовното преглеждане на вашата имплементация са от решаващо значение за поддържането на сигурно и стабилно уеб приложение.
Това изчерпателно ръководство предоставя солидна основа за разбиране и прилагане на CORS. Не забравяйте да се консултирате с официалната документация и ресурси за вашата конкретна сървърна технология, за да сте сигурни, че прилагате CORS правилно и сигурно.