For en erfaren udvikler er denne tekst måske slet ikke overraskende, men jeg synes, at mange af de artikler, jeg har læst om CORS-opsætningen i Rails, sagde noget i retning af: brug rack-cors, tillad enhver vært at få adgang til API'en, og (eventuelt): du bør overveje noget andet (end at tillade enhver vært) i produktionen.
Den foreslåede Kode var altid tæt på den nederste:
config/initializers/cors.rb
Rails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
origins ''
resource '', headers: :any, methods: :any
end
end
og desværre forklarede disse tekster os næppe, hvad vi rent faktisk skulle gøre i produktionen.
Jeg er ret god til at kopiere og indsætte (Jeg laver nogle gange sjov med, at virksomheder kunne ansætte en Stack Overflow copy-paster), for så vidt som der er et "tænk og juster"-øjeblik mellem "kopier" og "indsæt". Så jeg vil gerne uddybe lidt, hvad vi gør her, og hvordan det fungerer i det virkelige liv.
Jeg håber ikke, du har noget imod, at jeg starter med en kort introduktion til honor-teori og derefter går videre til Rails-eksemplerne.
Introduktion
Lad os starte fra begyndelsen. For at forklare tingene bedre har jeg delt introduktionen op i tre dele. Den første del vil skitsere, hvad en oprindelse er - nøglebegrebet for det, vi diskuterer her. Den anden handler om SOP, bare en kort beskrivelse. Og den sidste del handler om CORS
sig selv.
Hvad er en oprindelse?
Ifølge MDN Web Docs:
- Webindholdets oprindelse defineres af skemaet (protokollen), værten (domænet) og porten i den URL, der bruges til at få adgang til det. To objekter har kun samme oprindelse, når skemaet, værten og porten alle matcher (kilde)
Det virker ret klart, gør det ikke? Lad os analysere to eksempler fra MDN, bare for en sikkerheds skyld.
http://example.com/app1/index.html
, http://example.com/app2/index.html
De 2 ovenstående har samme oprindelse, fordi:
- Deres ordninger (http) er de samme,
- Deres domæner (example.com) er de samme,
- Deres porte (implicitte) er de samme.
http://www.example.com
, http://myapp.example.com
Disse 2 har forskellig oprindelse, fordi domænerne (www.example.com
, myapp.example.com
) er forskellige.
Jeg håber, det er tydeligt nok. Hvis ikke, kan du finde flere eksempler på MDN Web Docs.
Hvad er SOP?
MDN Web Docs siger (kilde):
- Politikken for samme oprindelse er en kritisk sikkerhedsmekanisme, der begrænser, hvordan et dokument eller script, der er indlæst fra én oprindelse, kan interagere med en ressource fra en anden oprindelse. Det hjælper med at isolere potentielt ondsindede dokumenter og reducere mulige angrebsvektorer.
- Skrivninger på tværs af oprindelse er typisk tilladt. Eksempler er links, omdirigeringer og indsendelse af formularer.
- Indlejring på tværs af oprindelse er typisk tilladt.
- Læsning på tværs af oprindelse er typisk ikke tilladt, men læseadgang lækkes ofte ved indlejring.
Brug CORS
for at tillade adgang på tværs af oprindelse
Som du kan se, er der meget om adfærd på tværs af oprindelse i definitionerne af SOP. Men det gør ikke noget. Alt, hvad vi bør vide nu, er, at den samme oprindelse har flere privilegier, og at vi kan løsne reglerne for cross-origins ved at bruge CORS. Og her kommer det næste afsnit ind i billedet.
Hvad er CORS?
Baseret på MDN's ord:
- Cross-Origin Resource Sharing (CORS) er en HTTP-header-baseret mekanisme, der gør det muligt for en server at angive enhver anden oprindelse (domæne, skema eller port) end sin egen, hvorfra en browser skal tillade indlæsning af ressourcer. CORS bygger også på en mekanisme, hvor browsere foretager en "preflight"-anmodning til den server, der er vært for cross-origin-ressourcen, for at kontrollere, at serveren vil tillade den faktiske anmodning. I denne preflight sender browseren headere, der angiver den HTTP-metode og de headere, der vil blive brugt i den faktiske anmodning (kilde).
Det er stadig ikke nok. Hvad der ikke blev sagt eksplicit, er, at den vigtigste header, når man bruger CORS
er Adgangskontrol-Allow-Origin
:
- Den
Adgangskontrol-Allow-Origin
svarheader angiver, om svaret kan deles med anmodende kode fra den givne oprindelse (kilde).
Det burde være det hele. I det virkelige liv, når man konfigurerer CORS
konfigurerer vi typisk ACAO
overskriften først.
Det virkelige liv
Det er alt, hvad der er af definitioner. Lad os vende tilbage til Rails og eksempler fra det virkelige liv.
Hvordan konfigureres CORS i Rails?
Vi vil helt sikkert bruge rack-cors (som vi fik besked på). Lad os genkalde os det første uddrag, det, der oftest findes i andre artikler:
config/initializers/cors.rb
Rails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
origins ''
resource '', headers: :any, methods: :any
end
end
Antallet af muligheder er stort eller endda uendeligt, men lad os se på disse to:
- Vi bygger den API, der kan bruges af tredjeparts browserklienter,
- Vi har en typisk frontend/backend-separation og ønsker at give vores betroede kunder adgang til API'et.
Opbygning af API med adgang for tredjepartsklienter
Hvis du står over for den første mulighed, kan du sandsynligvis vælge Oprindelse
'*' - du vil have andre til at bygge en klient oven på din API, og du ved ikke, hvem de er, vel?
Typisk adskillelse af frontend og backend
Hvis du udvikler sidstnævnte, vil du sandsynligvis ikke have, at alle skal lave cross-origin-anmodninger til din API. Det vil du hellere:
- give produktionsklienter adgang til produktions-API,
- Det samme gælder iscenesættelse,
- Det samme gælder for localhost,
- Det kan være en god idé at give FE review-apps adgang til staging.
Vi vil stadig bruge rack-cors (som vi fik besked på) - men på vores egen måde.
Lad os bruge 2 ENV-variabler: TILLADTE_OPRINDELSER
for bogstavelige oprindelsesdefinitioner (en stjerne eller en faktisk URL) og TILLADT_OPRINDELSE_REGEXPS
for mønstrene.
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)
ressource '*',
methods: %i[get post put patch delete options head],
overskrifter: :any
end
end
Hvad sker der her?
- Som du kan se, opdeler vi de værdier, der er defineret i ENV-variablerne, med forskellige separatorer. Det skyldes, at der er mindre sandsynlighed for, at et semikolon optræder i det mønster, der definerer URL'en.
- Bogstavelige værdier er klar til brug, men vi er nødt til at mappe mønstrene til at være faktiske Regexp-instanser.
- Derefter samler vi det hele og giver disse værter adgang til enhver ressource med hvidlistede metoder, som vores API bruger.
Det burde give dig nok fleksibilitet til at definere de rigtige værdier i dine udviklings-, staging- og produktionsmiljøer.
Konklusioner
Lad os opsummere alt det ovenstående i nøglepunkter:
- Brug ENV-variabler til at konfigurere
CORS
,
- Brug regulære udtryk til at give forskellige oprindelser adgang til staging-API'en (f.eks. til review-apps),
- Sæt altid "tænk og juster" mellem "kopier" og "sæt ind".
Det var det hele. Hav en god dag 🙂 .
Læs mere om det:
Hvorfor bør du (sandsynligvis) bruge Typescript?
10 NYC-startups, der er værd at nævne i 2021