Dla doświadczonego programisty ten tekst może nie być wcale zaskakujący, ale myślę, że wiele artykułów, które czytałem na temat konfiguracji CORS w Railsach, mówiło coś w rodzaju: użyj rack-cors, zezwól dowolnemu hostowi na dostęp do API i (opcjonalnie): powinieneś rozważyć coś innego (niż zezwolenie na dowolny host) w produkcji.
Proponowane kod był zawsze blisko tego poniżej:
config/initializers/cors.rb
Rails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
origins ''
resource '', headers: :any, methods: :any
end
end
i, niestety, teksty te nie wyjaśniały nam, co właściwie należy robić w produkcji.
Jestem całkiem w porządku z kopiowaniem i wklejaniem (Czasami żartuję, że firmy mogłyby zatrudnić kopiującego Stack Overflow), jeśli chodzi o moment "pomyśl i dostosuj" między "kopiuj" i "wklej". Chciałbym więc nieco rozwinąć to, co tutaj robimy i jak to działa w prawdziwym życiu.
Mam nadzieję, że nie masz nic przeciwko temu, że zacznę od krótkiego wprowadzenia do teorii honoru, a następnie przejdę do przykładów Railsów.
Wprowadzenie
Zacznijmy od początku. Aby lepiej to wyjaśnić, podzieliłem to wprowadzenie na trzy części. W pierwszej przedstawię, czym jest origin - kluczowy termin dla tego, o czym tutaj rozmawiamy. Druga dotyczy SOP, to tylko krótki opis. Ostatnia część mówi o CORS
siebie.
Co to jest pochodzenie?
Według MDN Web Docs:
- Pochodzenie treści internetowej jest definiowane przez schemat (protokół), host (domenę) i port adresu URL używanego do uzyskania do niej dostępu. Dwa obiekty mają to samo pochodzenie tylko wtedy, gdy schemat, host i port są zgodne (źródło)
Wydaje się to całkiem jasne, prawda? Na wszelki wypadek przeanalizujmy dwa przykłady z MDN.
http://example.com/app1/index.html
, http://example.com/app2/index.html
Powyższe 2 mają to samo pochodzenie, ponieważ:
- ich schematy (http) są takie same,
- ich domeny (example.com) są takie same,
- ich porty (domyślne) są takie same.
http://www.example.com
, http://myapp.example.com
Te 2 mają różne pochodzenie, ponieważ domeny (www.example.com
, myapp.example.com
) są różne.
Mam nadzieję, że jest to wystarczająco jasne. Jeśli nie, więcej przykładów można znaleźć w MDN Web Docs.
Co to jest SOP?
MDN Web Docs mówi (źródło):
- Polityka tego samego pochodzenia jest krytycznym mechanizmem bezpieczeństwa, który ogranicza sposób, w jaki dokument lub skrypt załadowany z jednego źródła może wchodzić w interakcję z zasobem z innego źródła. Pomaga izolować potencjalnie złośliwe dokumenty, ograniczając możliwe wektory ataku.
- Zazwyczaj dozwolone są zapisy między źródłami. Przykładami są linki, przekierowania i przesyłanie formularzy.
- Osadzanie między źródłami jest zazwyczaj dozwolone.
- Odczyty cross-origin są zazwyczaj niedozwolone, ale dostęp do odczytu jest często wyciekany przez osadzanie.
Użycie CORS
aby umożliwić dostęp między źródłami
Cóż, jak widać, w definicjach SOP znajduje się wiele informacji na temat zachowań związanych z różnymi źródłami. W porządku. Wszystko, co powinniśmy teraz wiedzieć, to to, że to samo pochodzenie ma więcej przywilejów i możemy poluzować zasady dla różnych źródeł, używając CORS. I tutaj pojawia się następna sekcja.
Czym jest CORS?
Opierając się na słowach MDN:
- Cross-Origin Resource Sharing (CORS) to mechanizm oparty na nagłówku HTTP, który pozwala serwerowi wskazać dowolne inne pochodzenie (domenę, schemat lub port) niż jego własne, z którego przeglądarka powinna zezwolić na ładowanie zasobów. CORS opiera się również na mechanizmie, za pomocą którego przeglądarki wykonują żądanie "preflight" do serwera hostującego zasób cross-origin, aby sprawdzić, czy serwer zezwoli na faktyczne żądanie. W tym preflight przeglądarka wysyła nagłówki, które wskazują metodę HTTP i nagłówki, które zostaną użyte w rzeczywistym żądaniu (źródło).
To wciąż za mało. To, co nie zostało tam wyraźnie powiedziane, to fakt, że najważniejszym nagłówkiem podczas korzystania z CORS
jest Access-Control-Allow-Origin
:
- The
Access-Control-Allow-Origin
nagłówek odpowiedzi wskazuje, czy odpowiedź może być współdzielona z kodem żądania z danego źródła (źródło).
Cóż, to powinno być wszystko. W prawdziwym życiu, podczas konfigurowania CORS
zazwyczaj konfigurujemy ACAO
nagłówek pierwszy.
Prawdziwe życie
To tyle, jeśli chodzi o definicje. Wróćmy do Railsów i przykładów z życia wziętych.
Jak skonfigurować CORS w Railsach?
Zdecydowanie użyjemy rack-corów (tak jak nam powiedziano). Przypomnijmy sobie pierwszy fragment, który jest najczęściej podawany w innych artykułach:
config/initializers/cors.rb
Rails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
origins ''
resource '', headers: :any, methods: :any
end
end
Liczba opcji jest ogromna, a nawet nieskończona, ale rozważmy te dwie:
- tworzymy API, które może być używane przez klientów przeglądarek innych firm,
- mamy typową separację frontend/backend i chcemy umożliwić naszym zaufanym klientom dostęp do API.
Budowanie API dostępnego dla klientów zewnętrznych
Jeśli masz do czynienia z pierwszą opcją, prawdopodobnie możesz wybrać początki
'*' - chcesz, aby inni zbudowali klienta na podstawie twojego API i nie wiesz, kim oni są, prawda?
Typowa separacja frontend/backend
Jeśli tworzysz to drugie rozwiązanie, prawdopodobnie nie chcesz, aby wszyscy wysyłali żądania typu cross-origin do Twojego API. Raczej chcesz:
- zezwalają klientom produkcyjnym na dostęp do produkcyjnego API,
- To samo dotyczy inscenizacji,
- to samo dla localhost,
- możesz zezwolić aplikacjom FE review na dostęp do staging.
Nadal będziemy używać rack-corów (tak jak nam kazano) - ale po naszemu.
Użyjmy 2 zmiennych ENV: ALLOWED_ORIGINS
dla dosłownych definicji pochodzenia (gwiazdka lub rzeczywisty adres URL) i ALLOWED_ORIGIN_REGEXPS
dla wzorów.
config/initializers/cors.rb
frozenstringliteral: true
toregexp = ->(string) { Regexp.new(string) }
hosts = [
*ENV.fetch('ALLOWEDORIGINS').split(','),
*ENV.fetch('ALLOWEDORIGINREGEXPS').split(';').map(&to_regexp)
]
Rails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
origins(*hosts)
resource '*',
methods: %i[get post put patch delete options head],
headers: :any
end
end
Co tu się dzieje?
- Jak widać, rozdzielamy wartości zdefiniowane w zmiennych ENV różnymi separatorami. Dzieje się tak, ponieważ średnik jest mniej prawdopodobny do pojawienia się we wzorcu definiującym adres URL.
- Dosłowne wartości są gotowe do użycia, ale musimy zmapować wzorce na rzeczywiste instancje Regexp.
- Następnie łączymy wszystko razem i zezwalamy tym hostom na dostęp do dowolnego zasobu za pomocą metod z białej listy, których używa nasz interfejs API.
Powinno to zapewnić wystarczającą elastyczność, aby zdefiniować odpowiednie wartości w środowiskach programistycznych, przejściowych i produkcyjnych.
Wnioski
Podsumujmy wszystkie powyższe w kluczowych punktach:
- używać zmiennych ENV do konfiguracji
CORS
,
- używać wyrażeń regularnych, aby umożliwić różnym źródłom dostęp do interfejsu API staging (np. w przypadku aplikacji do recenzowania),
- Zawsze umieszczaj "pomyśl i dostosuj" między "kopiuj" i "wklej".
To by było na tyle. Miłego dnia! 🙂
Czytaj więcej:
Dlaczego (prawdopodobnie) powinieneś używać Typescript?
10 startupów z Nowego Jorku, o których warto wspomnieć w 2021 roku