В тази статия виждаме как да активираме CORS (Cross-Origin Resource Sharing) с бисквитка HTTPOnly, за да защитим нашите токени за достъп.
В днешно време бекенд сървърите и предните клиенти се разполагат в различни домейни. Следователно сървърът трябва да активира CORS, за да позволи на клиентите да комуникират със сървъра в браузъри.
Освен това сървърите внедряват удостоверяване без състояние за по-добра скалируемост. Токените се съхраняват и поддържат от страната на клиента, но не и от страната на сървъра като сесията. От съображения за сигурност е по-добре да съхранявате токени в HTTPOnly бисквитки.
Съдържание
Защо са блокирани заявките за Cross-Origin?
Да приемем, че нашето приложение за интерфейс е внедрено на https://app.pctechbg.net.com. Скрипт, зареден в https://app.pctechbg.net.com, може да изисква само ресурси от същия произход.
Всеки път, когато се опитаме да изпратим заявка от кръстосан произход към друг домейн https://api.pctechbg.net.com или друг порт https://app.pctechbg.net.com:3000 или друга схема http://app.pctechbg.net.com, cross-origin заявката ще бъде блокирана от браузъра.
Но защо същата заявка, блокирана от браузъра, се изпраща от който и да е бекенд сървър с помощта на curl заявка или се изпраща с помощта на инструменти като пощальона без проблем с CORS. Всъщност това е за сигурност, за да защити потребителите от атаки като CSRF (Cross-Site Request Forgery).
Да вземем пример, да предположим, че някой потребител е влязъл в собствения си PayPal акаунт в своя браузър. Ако можем да изпратим заявка от кръстосан произход до paypal.com от скрипт, зареден на друг домейн malicious.com, без грешка/блокиране на CORS, както изпращаме заявка от същия произход.
Нападателите могат лесно да изпратят своята злонамерена страница https://malicious.com/transfer-money-to-attacker-account-from-user-paypal-account, като я преобразуват в кратък URL, за да скрият действителния URL. Когато потребителят щракне върху злонамерена връзка, скриптът, зареден в домейна malicious.com, ще изпрати заявка за кръстосан произход до PayPal за прехвърляне на потребителската сума към акаунта на атакуващия PayPal, който ще бъде изпълнен. Всички потребители, които са влезли в акаунта си в PayPal и са кликнали върху тази злонамерена връзка, ще загубят парите си. Всеки може лесно да открадне пари без познания за потребителски акаунт в PayPal.
Поради горепосочената причина, браузърите блокират всички заявки от кръстосан произход.
Какво е CORS (Cross-Origin Resource Sharing)?
CORS е механизъм за защита, базиран на заглавка, използван от сървъра, за да каже на браузъра да изпрати заявка от кръстосан произход от надеждни домейни.
Сървърът е активиран с CORS заглавки, използвани за избягване на заявки от кръстосан произход, блокирани от браузъри.
Как работи CORS?
Тъй като сървърът вече е дефинирал своя доверен домейн в своята CORS конфигурация. Когато изпратим заявка до сървъра, отговорът ще каже на браузъра, че исканият домейн е надежден или не в заглавката му.
Има два вида CORS заявки:
- Проста молба
- Предполетна заявка
Проста заявка:
- Браузърът изпраща заявката до кръстосан домейн с произход (https://app.pctechbg.net.com).
- Сървърът изпраща обратно съответния отговор с разрешени методи и разрешен произход.
- След като получи заявката, браузърът ще провери изпратената стойност на заглавката на източника (https://app.pctechbg.net.com) и получената стойност на access-control-allow-origin (https://app.pctechbg.net.com) са еднакви или заместващ знак
. В противен случай ще изведе CORS грешка.
- Заявка за предварителен полет:
- В зависимост от параметъра на персонализираната заявка от заявката от кръстосан произход, като методи (PUT, DELETE) или персонализирани заглавки или различен тип съдържание и т.н. Браузърът ще реши да изпрати предварителна заявка OPTIONS, за да провери дали действителната заявка е безопасна за изпращане или не.
След получаване на отговора (код на състоянието: 204, което означава, че няма съдържание), браузърът ще провери за параметрите за разрешаване на контрол на достъпа за действителната заявка. Ако параметрите на заявката са разрешени от сървъра. Изпратената и получена действителна заявка от друг произход
Ако access-control-allow-origin: *, тогава отговорът е разрешен за всички източници. Но не е безопасно, освен ако не ви трябва.
Как да активирам CORS?
За да активирате CORS за всеки домейн, активирайте CORS заглавки, за да разрешите произход, методи, персонализирани заглавки, идентификационни данни и т.н.
- Браузърът чете CORS хедъра от сървъра и позволява действителни заявки от клиента само след проверка на параметрите на заявката.
- Access-Control-Allow-Origin: За да посочите точни домейни (https://app.geekflate.com, https://lab.pctechbg.net.com) или заместващ знак
- Access-Control-Allow-Methods: За разрешаване на HTTP методите (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS), от които се нуждаем само ние.
- Access-Control-Allow-Headers: За разрешаване само на конкретни заглавки (Authorization, csrf-token)
- Access-Control-Allow-Credentials: Булева стойност, използвана за разрешаване на кръстосани идентификационни данни (бисквитки, заглавка за оторизация).
Access-Control-Max-Age: Указва на браузъра да кешира отговора преди полет за известно време.
Access-Control-Expose-Headers: Посочете заглавки, които са достъпни от клиентски скрипт.
За активиране на CORS в apache и Nginx уеб сървър, следвайте този урок.
const express = require('express'); const app = express() app.get('/users', function (req, res, next) { res.json({msg: 'user get'}) }); app.post('/users', function (req, res, next) { res.json({msg: 'user create'}) }); app.put('/users', function (req, res, next) { res.json({msg: 'User update'}) }); app.listen(80, function () { console.log('CORS-enabled web server listening on port 80') })
Активиране на CORS в ExpressJS
Да вземем примерно приложение ExpressJS без CORS:
npm install cors
В горния пример сме активирали потребителска крайна точка на API за методите POST, PUT, GET, но не и метода DELETE.
За лесно активиране на CORS в приложението ExpressJS можете да инсталирате cors
app.use(cors({ origin: '*' }));
Access-Control-Allow-Origin
app.use(cors({ origin: 'https://app.pctechbg.net.com' }));
Активиране на CORS за всички домейни
app.use(cors({ origin: [ 'https://app.geekflare.com', 'https://lab.geekflare.com' ] }));
Активиране на CORS за един домейн
Ако искате да разрешите CORS за произход https://app.pctechbg.net.com и https://lab.pctechbg.net.com
app.use(cors({ origin: [ 'https://app.geekflare.com', 'https://lab.geekflare.com' ], methods: ['GET', 'PUT', 'POST'] }));
Access-Control-Allow-Methods
За да активирате CORS за всички методи, пропуснете тази опция в модула CORS в ExpressJS. Но за активиране на специфични методи (GET, POST, PUT).
app.use(cors({ origin: [ 'https://app.geekflare.com', 'https://lab.geekflare.com' ], methods: ['GET', 'PUT', 'POST'], allowedHeaders: ['Content-Type', 'Authorization', 'x-csrf-token'] }));
Access-Control-Allow-Headers
Използва се за разрешаване на заглавки, различни от тези по подразбиране, за изпращане с действителни заявки.
app.use(cors({ origin: [ 'https://app.geekflare.com', 'https://lab.geekflare.com' ], methods: ['GET', 'PUT', 'POST'], allowedHeaders: ['Content-Type', 'Authorization', 'x-csrf-token'], credentials: true }));
Access-Control-Allow-Credentials
Пропуснете това, ако не искате да кажете на браузъра да разрешава идентификационни данни при поискване, дори когато withCredentials е зададено на true.
app.use(cors({ origin: [ 'https://app.geekflare.com', 'https://lab.geekflare.com' ], methods: ['GET', 'PUT', 'POST'], allowedHeaders: ['Content-Type', 'Authorization', 'x-csrf-token'], credentials: true, maxAge: 600 }));
Access-Control-Max-Age
За да накарате браузъра да кешира информацията за отговор преди полет в кеша за определена секунда. Пропуснете това, ако не искате да кеширате отговора.
app.use(cors({ origin: [ 'https://app.geekflare.com', 'https://lab.geekflare.com' ], methods: ['GET', 'PUT', 'POST'], allowedHeaders: ['Content-Type', 'Authorization', 'x-csrf-token'], credentials: true, maxAge: 600, exposedHeaders: ['Content-Range', 'X-Content-Range'] }));
Кешираният отговор преди полет ще бъде достъпен за 10 минути в браузъра.
app.use(cors({ origin: [ 'https://app.geekflare.com', 'https://lab.geekflare.com' ], methods: ['GET', 'PUT', 'POST'], allowedHeaders: ['Content-Type', 'Authorization', 'x-csrf-token'], credentials: true, maxAge: 600, exposedHeaders: ['*', 'Authorization', ] }));
Access-Control-Expose-Headers
Ако поставим заместващия знак
в exposedHeaders, няма да изложи заглавката на Authorization. Така че трябва да изложим изрично, както е показано по-долу
Горното ще разкрие всички заглавки и заглавката за оторизация също.
- Какво е HTTP бисквитка?
- Бисквитката е малка част от данните, които сървърът ще изпрати на клиентския браузър. При по-късни заявки браузърът ще изпраща всички бисквитки, свързани със същия домейн при всяка заявка.
- Бисквитката има свой атрибут, който може да бъде дефиниран, за да накара бисквитката да работи по различен начин според нуждите ни.
- Име Име на бисквитката.
- стойност: данни на бисквитката, съответстващи на името на бисквитката
- Домейн: бисквитките ще бъдат изпратени само до дефинирания домейн
- Път: бисквитките се изпращат само след дефинирания URL префикс път. Да предположим, че сме дефинирали нашия път към бисквитката като path=’admin/’. Бисквитките не се изпращат за URL адреса https://pctechbg.net.com/expire/, но се изпращат с URL префикс https://pctechbg.net.com/admin/
- Максимална възраст/изтича (число в секунда): Кога трябва да изтече бисквитката. Животът на бисквитката прави бисквитката невалидна след определеното време. [Strict, Lax, None]HTTPOnly(Boolean): Backend сървърът има достъп до тази бисквитка HTTPOnly, но не и до скрипта от страна на клиента, когато е вярно. Сигурен (булев): Бисквитките се изпращат само през SSL/TLS домейн, когато са вярно.същия сайт (низ
): Използва се за активиране/ограничаване на бисквитките, изпратени при заявки между сайтове. За да научите повече подробности относно бисквитките, вижте същия сайт
MDN
. Приема три опции Строг, Лек, Няма. Стойността за защита на бисквитката е зададена на true за конфигурацията на бисквитката sameSite=Няма.
Защо HTTPOnly бисквитка за токени?
Съхраняването на токена за достъп, изпратен от сървъра в хранилище от страна на клиента, като локално хранилище, индексирана DB и бисквитка (HTTPOnly не е зададено на true), е по-уязвимо за XSS атака. Да предположим, че някоя от вашите страници е слаба на XSS атака. Нападателите могат да злоупотребят с потребителски токени, съхранени в браузъра.
HTTPOnly бисквитките се задават/получават само от сървър/бекенд, но не и от страна на клиента.
- Скриптът от страна на клиента е ограничен за достъп до тази бисквитка само за HTTP. Така че HTTPOnly бисквитките не са уязвими на XSS атаки и са по-сигурни. Тъй като е достъпен само от сървъра.
- Активирайте бисквитката HTTPOnly в бекенда с активиран CORS
- Активирането на бисквитка в CORS изисква конфигурацията по-долу в приложението/сървъра.
- Задайте заглавката на Access-Control-Allow-Credentials на true.
Access-Control-Allow-Origin и Access-Control-Allow-Headers не трябва да са заместващи символи
const express = require('express'); const app = express(); const cors = require('cors'); app.use(cors({ origin: [ 'https://app.geekflare.com', 'https://lab.geekflare.com' ], methods: ['GET', 'PUT', 'POST'], allowedHeaders: ['Content-Type', 'Authorization', 'x-csrf-token'], credentials: true, maxAge: 600, exposedHeaders: ['*', 'Authorization' ] })); app.post('/login', function (req, res, next) { res.cookie('access_token', access_token, { expires: new Date(Date.now() + (3600 * 1000 * 24 * 180 * 1)), //second min hour days year secure: true, // set to true if your using https or samesite is none httpOnly: true, // backend only sameSite: 'none' // set to none for cross-request }); res.json({ msg: 'Login Successfully', access_token }); }); app.listen(80, function () { console.log('CORS-enabled web server listening on port 80') });
.
Атрибутът на бисквитката sameSite трябва да бъде None.
За да активирате стойността на sameSite на none, задайте защитената стойност на true: Разрешете бекенда със SSL/TLS сертификат, за да работи в името на домейна.
Нека видим примерен код, който задава токен за достъп в HTTPOnly бисквитка след проверка на идентификационните данни за вход.
Можете да конфигурирате CORS и HTTPOnly бисквитки, като приложите горните четири стъпки във вашия бекенд език и уеб сървър.
var xhr = new XMLHttpRequest(); xhr.open('GET', 'http://api.pctechbg.net.com/user', true); xhr.withCredentials = true; xhr.send(null);
Можете да следвате този урок за apache и Nginx за активиране на CORS, като следвате горните стъпки.
fetch('http://api.pctechbg.net.com/user', { credentials: 'include' });
с идентификационни данни за заявка Cross-Origin
$.ajax({ url: 'http://api.pctechbg.net.com/user', xhrFields: { withCredentials: true } });
Идентификационни данни (бисквитка, оторизация), изпратени по подразбиране със заявка със същия произход. За cross-origin трябва да посочим withCredentials на true.
axios.defaults.withCredentials = true
API на XMLHttpRequest
API за извличане
JQuery AjaxАксиосЗаключение Надявам се, че горната статия ви помага да разберете как работи CORS и да активирате CORS за заявки от кръстосан произход в сървъра. Защо съхраняването на бисквитки в HTTPOnly е сигурно и как с идентификационните данни, използвани в клиентите за кръстосани заявки.