Dziś chciałbym podzielić się z Tobą wpisem z pogranicza backendu i frontendu – czyli o tym, czym jest Cross-Origin Resource Sharing (CORS). Szczegółowo omówimy zagadnienie CORS oraz wpływ na bezpieczeństwo klientów przeglądarek.
Z tego artykułu dowiesz się:
- Czym jest CORS?
- Jak CORS wpływa na bezpieczeństwo web aplikacji?
- Czym są zapytania proste?
- Czym są zapytania złożone?
- W jakich krokach przeglądarka wykonuje zapytania proste i złożone?
Przed przeczytaniem tego artykułu powinieneś wiedzieć:
- Czym jest Same-Origin Policy (SOP).
- Czym jest Origin.
- Jakie są ograniczenia Same-Origin Policy.
O czym przeczytasz w artykule: Same-Origin Policy (SOP) a bezpieczeństwo www
Czym jest CORS?
CORS (czyli Cross-Origin Resource Sharing) to mechanizm bezpieczeństwa, który wykorzystuje dodatkowe nagłówki HTTP, aby poinformować przeglądarkę czy ma udostępnić dane zwrócone klientowi (zapytanie wykona się poprawnie, więc odpowiedź i tak będzie). Serwer decyduje czy klient jest klientem zaufanym i na tej podstawie ustawia odpowiednie nagłówki. Dzięki tym nagłówkom przeglądarka wie, czy ma udostępnić dane klientowi. Jeśli to samo zapytanie wykonalibyśmy za pomocą curl czy Postman, od razu widzielibyśmy odpowiedź serwera (interesujące nas body).
Wyobraźmy sobie system z kilkoma subdomenami, np. shop.softwareskill.pl oraz payments.softwareskill.pl. Mimo że domeny z wielkim prawdopodobieństwem są zarządzane przez tę samą jednostkę, komunikacja pomiędzy nimi będzie zablokowana przez politykę Same-Origin Policy (ponieważ origin jest różny). Funkcjonalność płatności zostanie więc zablokowana przez przeglądarkę.
Kolejnym przykładem mogą być dwie osobne aplikacje, które komunikują się pomiędzy sobą za pomocą RestAPI. Aplikacja frontendowa, postawiona na środowisku lokalnym programisty nie będzie mogła komunikować się z RestAPI serwowanym przez serwer developerski (inny origin). Znów polityka SOP zablokuje żądania przeglądarki.
To oczywiście niepełna lista przypadków blokowania requestów HTTP przez przeglądarki. Wyjściem z sytuacji jest CORS, który umożliwia bezpieczne wykonywanie zapytań HTTP Cross-Origin. Bezpieczne – czyli dajemy możliwość stronie serwującej dane (serwerowi) zdecydowania czy ufa stronie klienckiej i czy w związku z tym dane, które serwer wyśle klientowi, jako odpowiedź mają być dla niego dostępne. Brzmi skomplikowanie? W następnych akapitach wszystko wyjaśnimy.
Zapytania proste a złożone
Przeglądarka dzieli zapytania HTTP na proste i złożone. W zależności od przydziału obsługa zapytania jest inna. Zapytania proste to takie, które możemy wykonywać w inny sposób niż poprzez XHR – np. umieszczając zapytanie GET w tagu <img> lub poprzez samo wysyłające się formularze. Nie ma potrzeby budowania dodatkowych zabezpieczeń (w kontekście CORS), ponieważ przeglądarka nie jest w stanie odczytać i wyświetlić odpowiedzi serwera klientowi. Drugi rodzaj zapytań HTTP to zapytania złożone – umożliwiają one interakcję z odpowiedzią serwera. A więc możemy np. podmienić zawartość strony WWW, czy załadować jakiś złośliwy skrypt JavaScript. I tutaj do gry wchodzi polityka SOP przeglądarki, blokując potencjalnie niebezpieczne requesty, które są z innego origin. Często jednak potrzebujemy komunikacji zapytań złożonych pomimo innego origin. Przeglądarka wówczas odpytuje serwer w dwóch krokach, o czym opowiemy w kolejnych akapitach.
Zapytania proste
Zapytania proste są zdefiniowane wykorzystywanymi metodami czy nagłówkami HTTP. Metody HTTP należące do zapytań prostych:
- HEAD
- GET
- POST
Nagłówki HTTP pochodzą ze zbioru:
- Accept
- Accept-Language
- Content-Language
- Content-Type (dla wartości: text/plain, multipart/form-data, application/x-www-form-urlencoded)
A także, rzadziej spotykane:
- DPR
- Downlink
- Save-Data
- Width
- View-port-Width
Jak przeglądarka wykonuje zapytania proste?
Prześledźmy w jakich krokach przeglądarka obsługuje zapytania proste i jaki wpływ na ostateczny rezultat ma serwer.
- Użytkownik żąda od przeglądarki załadowania strony https://softwareskill.pl.
- Przeglądarka wysyła proste zapytanie GET do serwera.
- Serwer w odpowiedzi zwraca dokument HTML przeglądarce.
- W źródle strony (zwrócony HTML w poprzednim kroku) znajduje się kod JavaScript, który potrzebuje skomunikować się z API pod adresem https://externalapi.pl aby zaciągnąć dodatkowe dane (np. dane pogodowe, polecane produkty, etc.). Przeglądarka weryfikuje czy ma do czynienia z zapytaniem prostym, czy złożonym. W tym przypadku jest to zapytanie proste (mamy proste zapytanie GET). Przeglądarka zachowuje się więc ta samo jak w przypadku normalnych zapytań Same-Origin, z jednym wyjątkiem – dołącza nagłówek Origin, wskazujący na origin klienta – w tym przypadku nagłówek wyglądałby następująco Origin: https://softwareskill.pl. Nagłówek ten obowiązkowy jest w zapytaniach Cross-Origin, ale w zapytaniach Same-Origin jest opcjonalny.
- Serwer otrzymuje zapytanie. Dzięki temu, że ma uzupełniony nagłówek Origin, jest w stanie zdecydować czy ufa temu konkretnemu klientowi, czy nie. Jeśli mu nie ufa, nie ustawia żadnych dodatkowych nagłówków i zapytanie wykonuje się w normalnym trybie. Wówczas przeglądarka otrzyma odpowiedź, ale jej nie wyświetli klientowi (SOP). Jeśli natomiast serwer ufa klientowi z origin https://softwareskill.pl w odpowiedzi dołączy nagłówki z serii Access-Control (o czym więcej za chwilkę). Najważniejszym nagłówkiem jest Access-Control-Allow-Origin.
- W ostatnim kroku, przeglądarka otrzymuje odpowiedź i analizuje zwrócone nagłówki z serii Access-Control. Jeśli wszystko się zgadza, przeglądarka wyświetla odpowiedź klientowi, jeśli nie – zgłasza popularny błąd
Failed to load https://softwareskill.pl: no 'Access-Control-Allow-Origin’ header is present on the requested resource. Origin ’https://externalapi.pl’ is therefore not allowed access.
Powyższy błąd oznacza tyle, że o ile zapytanie XHR zostało wykonane, o tyle przeglądarka zablokowała wyświetlenie odpowiedzi klientowi. Warto jest dodać, że za pomocą konsoli developerskiej w chrome, curl czy Postman moglibyśmy wyciągnąć odpowiedź. Na poniższej fotografii prezentujemy inny przykład zapytania prostego.
Nagłówki wykorzystywane w zapytaniach prostych
CORS jest więc możliwy dzięki nagłówkom z serii Access-Control oraz Origin. Nagłówków tych jest więcej, ale w zapytaniach prostych są używane:
Zauważ, że CORS jest mechanizmem kompatybilnym wstecz. Serwer nieświadomy istnienia CORS, zwróci po prostu zwykłą odpowiedź (można to symulować w Postmanie)
Zapytania złożone
Wiemy już, czym są zapytania proste. Przyjrzyjmy się bardziej szczegółowo zapytaniom złożonym.
- Użytkownik prosi przeglądarkę o wyświetlenie strony https://softwareskill.pl
- Przeglądarka wysyła proste zapytanie GET do serwera.
- Serwer w odpowiedzi zwraca dokument HTML przeglądarce.
- Ponownie, serwer potrzebuje skomunikować się z RestAPI dostępnym pod innym originem (aby np. dociągnąć dane o reklamach ofert sponsorowanych). W celu wykonania zapytania używane jest API przeglądarki, a konkretniej obiekt XMLHttpRequest. Przeglądarka weryfikuje, czy oby na pewno mamy doczynienia z zapytaniem prostym (w tym przypadku do zapytania jest dodany customowy nagłówek X-Custom). Dodatkowy nagłówek nie występuje na liście nagłówków dostępnej dla zapytań prostych. Przeglądarka musi się upewnić czy zapytania Cross-Origin są obsługiwane przez serwer. W tym celu wykonuje tak zwany preflight request. Zapytanie to charakteryzuje się kilkoma cechami. Po pierwsze adres, pod który wykonywane jest zapytanie, jest identyczny jak adres docelowego zapytania. Jest to metoda typu HTTP OPTIONS. W zapytaniu musi być obecny nagłówek Access-Control-Request-Method, a także (jeśli używamy nagłówków spoza zakresu listy nagłówków dostępnych dla zapytań prostych) Access-Control-request-Headers. Warto jest dodać, że zapytanie typu preflight jest automatycznie wykonywane przez przeglądarkę – z punktu widzenia programisty nie trzeba tego dodatkowo oprogramowywać.
- Serwer otrzymuje zapytanie preflight z kompletem informacji, na podstawie których może stwierdzić czy ma doczynienia z zaufanym klientem i czy chce obsłużyć zapytanie. Decyzja zostaje podjęta zgodnie z logiką aplikacji, a następnie odpowiedź jest zwracana do przeglądarki. Jeśli serwer obsługuje zapytania Cross-Origin i zgadza się na wykonanie zapytania – odpowiedź musi zawierać nagłówki Access-Control-Allow-Origin, Access-Control-Allow-Methods oraz Access-Control-Allow-Headers (ten nagłówek serwer musi dodać tylko w przypadku, kiedy zapytanie prefilght zawierało nagłówek Access-Control-Request-Headers).
- Przeglądarka dostaje odpowiedź na zapytanie preflight. W następnej kolejności przeglądarka sprawdza, czy odpowiednio zostały ustawione nagłówki z serii AC**. Jeśli przeglądarka wykryje jakikolwiek błąd tj. brak ustawionych nagłówków, nagłówki są ustawione, lecz nie zgadza się origin czy metoda HTTP – wyrzuca błąd i nie wysyła docelowego zapytania. Kiedy wszystko się zgadza, przeglądarka dopiero teraz wykonuje oryginalne zapytanie, które chciał wykonać klient (ponieważ zapytanie jest Cross-Origin – musi ono zawierać nagłówek Origin).
- Serwer dostaje oryginalne zapytanie (w tym momencie może zaufać klientowi, bo wie, że pochodzi z zaufanego źródła – inaczej zapytanie zostałoby zablokowane przez przeglądarkę w kroku 6). Warto zauważyć, że w poprzednich krokach sprawdzaliśmy jedynie, czy pozwolimy przeglądarce wykonać zapytanie. Nie było do tej pory mowy o tym czy zwrócone dane powinny być dostępne dla klienta. Jeśli chcemy dać możliwość dostępu do danych – po raz kolejny musimy ustawić nagłówek Access-Control-Allow-Origin.
- Ostatecznie przeglądarka dostaje odpowiedź z serwera i weryfikuje obecność i poprawność nagłówków z serii AC**. Jeśli wszystko się zgadza – zwraca dane do użytkownika ładując stronę. W przeciwnym przypadku zostaje zgłoszony błąd, a strona nie może być załadowana.
Warto dodać, że po raz kolejny mamy do czynienia ze wsteczną kompatybilnością. Jeśli serwer nie jest świadomy istnienia CORS – na zapytanie preflight odpowie bez nagłówków z serii AC**. Brak tych nagłówków jest traktowany przez przeglądarkę jako odpowiedź negatywna. Na poniższej fotografii prezentujemy inny przykład schematu wykonania zapytania złożonego.
Nagłówki wykorzystywane w zapytaniach złożonych
W zapytaniach złożonych wykorzystywane są nagłówki:
Jak CORS wpływa na bezpieczeństwo web aplikacji?
Przeglądarka, dzięki zapytaniom typu Cors-Origin zabezpiecza swoich użytkowników przed wykonaniem złośliwego skryptu JavaScript z innego originu. Jeśli serwer stwierdzi, że zapytanie nie jest zaufane, wyśle odpowiedź do przeglądarki (z odpowiednimi nagłówkami), a ta z kolei, po analizie nagłówków z odpowiedzi – nie wyświetli zwróconych danych użytkownikowi (mogłyby to być dane, służące do podmiany części strony, zawierającej np. formularz). Przykłady można mnożyć w nieskończoność. Ale zapamiętaj drogi czytelniku, że Cors-Origin i SOP to podstawowe mechanizmy bezpieczeństwa web aplikacji.
Zakończenie
Drogi czytelniku. Już wiesz, czym się różnią zapytania proste i złożone (preflight). Po przeczytaniu tego długaśnego artykułu masz większą świadomość, jak działają nagłówki i zapytania Cors-Origin. Jeśli podobał Ci się artykuł – koniecznie podziel się nim ze swoimi znajomymi! Dowiedz się również Jak skonfigurować CORS w Spring? Jeśli spodobał Ci się artykuł – podziel się nim ze swoimi znajomymi oraz koniecznie zapisz się do naszego newslettera – tak, aby nie ominął Cię żaden mięsny artykuł 🙂
Gdybyś potrzebował jeszcze więcej:
Jesteś Java Developerem?
Przejdź na wyższy poziom wiedzy
„Droga do Seniora” 🔥💪
Jesteś Team Leaderem? Masz zespół?
Podnieś efektywność i wiedzę swojego zespołu 👌
Szybkie podsumowanie
CORS (czyli Cross-Origin Resource Sharing) to mechanizm bezpieczeństwa, który wykorzystuje dodatkowe nagłówki HTTP, aby poinformować przeglądarkę, czy ma udostępnić dane zwrócone klientowi (zapytanie wykona się poprawnie, więc odpowiedź i tak będzie). Serwer decyduje czy klient jest klientem zaufanym i na tej podstawie ustawia odpowiednie nagłówki. Dzięki tym nagłówkom przeglądarka wie, czy ma udostępnić dane klientowi.
Przeglądarka, dzięki zapytaniom typu Cors-Origin zabezpiecza swoich użytkowników przed wykonaniem złośliwego skryptu JavaScript z innego originu. Jeśli serwer stwierdzi, że zapytanie nie jest zaufane, wyśle odpowiedź do przeglądarki (z odpowiednimi nagłówkami), a ta z kolei, po analizie nagłówków z odpowiedzi – nie wyświetli zwróconych danych użytkownikowi (mogłyby to być dane, służące do podmiany części strony, zawierającej np. formularz).
Obrazek: Designed by fanjianhua / Freepik