Ustaw pliki cookie dla żądań z różnych źródeł


103

Jak udostępniać pliki cookie z różnych źródeł? Dokładniej, jak używać Set-Cookienagłówka w połączeniu z nagłówkiem Access-Control-Allow-Origin?

Oto wyjaśnienie mojej sytuacji:

Próbuję ustawić plik cookie dla interfejsu API, który działa localhost:4000w aplikacji sieci Web, która jest hostowana localhost:3000.

Wygląda na to, że otrzymuję odpowiednie nagłówki odpowiedzi w przeglądarce, ale niestety nie mają one znaczenia. Oto nagłówki odpowiedzi:

HTTP / 1.1 200 OK
Access-Control-Allow-Origin: http: // localhost: 3000
Różne: pochodzenie, akceptacja-kodowanie
Set-Cookie: token = 0d522ba17e130d6d19eb9c25b7ac58387b798639f81ffe75bd449afbc3cc715d6b038e426adeac3316f0511dc7fae3f7; Max-Age = 86400; Domena = localhost: 4000; Ścieżka = /; Wygasa = wt., 19 września 2017 21:11:36 GMT; HttpOnly
Content-Type: application / json; charset = utf-8
Długość treści: 180
ETag: W / "b4-VNrmF4xNeHGeLrGehNZTQNwAaUQ"
Data: pon., 18 września 2017 r., 21:11:36 czasu GMT
Połączenie: utrzymuj przy życiu

Co więcej, widzę plik cookie pod, Response Cookiesgdy sprawdzam ruch za pomocą karty Sieć w narzędziach programistycznych Chrome. Jednak nie widzę, aby plik cookie został ustawiony na karcie Aplikacja poniżej Storage/Cookies. Nie widzę żadnych błędów CORS, więc zakładam, że brakuje mi czegoś innego.

Jakieś sugestie?

Aktualizacja I:

Używam modułu żądania w aplikacji React-Redux do wysyłania żądania do /signinpunktu końcowego na serwerze. Do serwera używam express.

Serwer ekspresowy:

res.cookie ('token', 'xxx-xxx-xxx', {maxAge: 86400000, httpOnly: true, domain: 'localhost: 3000'})

Żądanie w przeglądarce:

request.post ({uri: '/ signin', json: {nazwa_użytkownika: 'userOne', hasło: '123456'}}, (err, response, body) => {
    // Robi coś
})

Aktualizacja II:

Ustawiam teraz nagłówki żądań i odpowiedzi jak szalone, upewniając się, że są one obecne zarówno w żądaniu, jak i odpowiedzi. Poniżej zrzut ekranu. Zauważ, nagłówki Access-Control-Allow-Credentials, Access-Control-Allow-Headers, Access-Control-Allow-Methodsi Access-Control-Allow-Origin. Patrząc na problem, który znalazłem na githubie Axiosa , mam wrażenie, że wszystkie wymagane nagłówki są już ustawione. Jednak nadal nie ma szczęścia ...

wprowadź opis obrazu tutaj


4
@PimHeijden spójrz na to: developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/ ... może potrzebujesz withCredentials?
Kalamarico

2
Ok, używasz request i myślę, że to nie jest najlepszy wybór, spójrz na ten post i odpowiedź, axios myślę, że może ci się przydać. stackoverflow.com/questions/39794895/…
Kalamarico

Dzięki! Nie zauważyłem, że requestmoduł nie jest przeznaczony do użytku w przeglądarce. Jak dotąd Axios wydaje się wykonywać świetną robotę. Otrzymuję teraz zarówno nagłówek: Access-Control-Allow-Credentials:truei Access-Control-Allow-Origin:http://localhost:3000(używany do włączania CORS). Wydaje się to słuszne, ale Set-Cookienagłówek nic nie robi ...
Pim Heijden

Ten sam problem, ale używając bezpośrednio Axios: stackoverflow.com/q/43002444/488666 . Chociaż { withCredentials: true }jest to rzeczywiście wymagane przez stronę Axios, nagłówki serwerów również muszą być dokładnie sprawdzone (patrz stackoverflow.com/a/48231372/488666 )
Frosty Z

jakie nagłówki serwera?
Pim Heijden

Odpowiedzi:


169

Co musisz zrobić

Aby umożliwić pomyślne odbieranie i wysyłanie plików cookie przez żądanie CORS, wykonaj następujące czynności.

Back-end (serwer): ustaw Access-Control-Allow-Credentialswartość nagłówka HTTP na true. Upewnij się również, że nagłówki HTTP Access-Control-Allow-Origini Access-Control-Allow-Headerssą ustawione, a nie z symbolem wieloznacznym* .

Aby uzyskać więcej informacji na temat ustawiania CORS w express js, przeczytaj dokumentację tutaj

Front-end (klient): ustaw XMLHttpRequest.withCredentialsflagę na true, można to osiągnąć na różne sposoby w zależności od używanej biblioteki żądanie-odpowiedź:

Lub

Unikaj używania CORS w połączeniu z plikami cookie. Możesz to osiągnąć za pomocą proxy.

Jeśli z jakiegoś powodu nie unikaj tego. Rozwiązanie jest powyżej.

Okazało się, że Chrome nie ustawi ciasteczka, jeśli domena zawiera port. Ustawienie go na localhost(bez portu) nie stanowi problemu. Wielkie dzięki dla Erwina za tę wskazówkę!


2
Myślę, że masz ten problem tylko z powodu localhostsprawdzenia tego tutaj: stackoverflow.com/a/1188145, a także może to pomóc w twojej sprawie ( stackoverflow.com/questions/50966861/… )
Edwin

5
Ta odpowiedź bardzo mi pomogła! Znalezienie go zajęło dużo czasu. Ale myślę, że odpowiedź powinna wspomnieć, że ustawienie Access-Control-Allow-Originw wyraźnej domenie, nie tylko, "*"jest również wymagane. Wtedy byłaby to idealna odpowiedź
Dan

6
to jest dobra odpowiedź, a cała konfiguracja CORS-a, nagłówków, zaplecza i interfejsu użytkownika oraz unikanie lokalnego hosta z zastąpieniem / etc / hosts lokalnie z prawdziwą subdomeną, nadal widzę, że listonosz pokazuje SET-COOKIE w nagłówkach odpowiedzi, ale debugowanie chrome nie działa pokaż to w nagłówkach odpowiedzi, a plik cookie nie jest w rzeczywistości ustawiony w chrome. Jakieś inne pomysły do ​​sprawdzenia?
bjm88

1
@ bjm88 Czy w końcu doszedłeś do tego? Jestem w dokładnie tej samej sytuacji. Plik cookie jest ustawiony poprawnie podczas łączenia się z localhost: 3010 do localhost: 5001, ale nie działa z localhost: 3010 do fakeremote: 5001 (co wskazuje na 127.0.0.1 w moim pliku hosts). Dokładnie tak samo jest, gdy hostuję swój serwer na prawdziwym serwerze z niestandardową domeną (łącząc się z localhost: 3010 do mydomain.com). Zrobiłem wszystko, co jest zalecane w tej odpowiedzi, i próbowałem wielu innych rzeczy.
Formularz

1
W Angular, wymaganą zmianą po stronie klienta jest również dodanie withCredentials: true do opcji przekazanych do HttpClient.post, .get, options itp.
Marvin

18

Uwaga dotycząca przeglądarki Chrome wydana w 2020 roku.

Przyszła wersja przeglądarki Chrome będzie dostarczać pliki cookie z żądaniami między witrynami tylko wtedy, gdy są ustawione za pomocą SameSite=Nonei Secure.

Jeśli więc Twój serwer zaplecza nie ustawi SameSite = None, Chrome użyje domyślnie SameSite = Lax i nie będzie używać tego pliku cookie z żądaniami {withCredentials: true}.

Więcej informacji https://www.chromium.org/updates/same-site .

Programiści Firefox i Edge również chcą udostępnić tę funkcję w przyszłości.

Specyfikację można znaleźć tutaj: https://tools.ietf.org/html/draft-west-cookie-incrementalism-01#page-8


5
zapewnienie samesite = none i bezpiecznej flagi wymaga protokołu HTTPS. Jak to osiągnąć w systemie lokalnym, w którym HTTPS nie wchodzi w grę? czy możemy jakoś ominąć?
nirmal patel

@nirmalpatel Po prostu usuń wartość „Lax” w konsoli programisty Chome.
LennyLip

5

Aby klient mógł odczytywać pliki cookie z żądań między źródłami, musisz mieć:

  1. Wszystkie odpowiedzi z serwera muszą mieć w nagłówku:

    Access-Control-Allow-Credentials: true

  2. Klient musi wysłać wszystkie żądania z withCredentials: trueopcją

W mojej implementacji z Angular 7 i Spring Boot osiągnąłem to dzięki następującym rozwiązaniom:


Po stronie serwera:

@CrossOrigin(origins = "http://my-cross-origin-url.com", allowCredentials = "true")
@Controller
@RequestMapping(path = "/something")
public class SomethingController {
  ...
}

origins = "http://my-cross-origin-url.com"Część będzie dodać Access-Control-Allow-Origin: http://my-cross-origin-url.comdo nagłówka odpowiedzi każdego serwera

allowCredentials = "true"Część będzie dodać Access-Control-Allow-Credentials: truedo nagłówka odpowiedzi każdy serwer, który jest to, czego potrzebujemy, aby klient odczytywanie plików cookie


Strona klienta:

import { HttpInterceptor, HttpXsrfTokenExtractor, HttpRequest, HttpHandler, HttpEvent } from "@angular/common/http";
import { Injectable } from "@angular/core";
import { Observable } from 'rxjs';

@Injectable()
export class CustomHttpInterceptor implements HttpInterceptor {

    constructor(private tokenExtractor: HttpXsrfTokenExtractor) {
    }

    intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        // send request with credential options in order to be able to read cross-origin cookies
        req = req.clone({ withCredentials: true });

        // return XSRF-TOKEN in each request's header (anti-CSRF security)
        const headerName = 'X-XSRF-TOKEN';
        let token = this.tokenExtractor.getToken() as string;
        if (token !== null && !req.headers.has(headerName)) {
            req = req.clone({ headers: req.headers.set(headerName, token) });
        }
        return next.handle(req);
    }
}

Dzięki tej klasie faktycznie dodajesz dodatkowe elementy do wszystkich swoich żądań.

Pierwsza część req = req.clone({ withCredentials: true });dotyczy tego, czego potrzebujesz, aby wysłać każde zapytanie z withCredentials: trueopcją. W praktyce oznacza to, że najpierw zostanie wysłane żądanie OPCJI, aby uzyskać między nimi pliki cookie i token autoryzacyjny, przed wysłaniem rzeczywistych żądań POST / PUT / DELETE, które wymagają dołączenia do nich tego tokena (w nagłówku), w zlecić serwerowi weryfikację i wykonanie żądania.

Druga część to ta, która w szczególności obsługuje token anty-CSRF dla wszystkich żądań. W razie potrzeby odczytuje je z pliku cookie i zapisuje w nagłówku każdego żądania.

Pożądany wynik jest mniej więcej taki:

odpowiedź żądanie


co ta odpowiedź dodaje do istniejącej?
Pim Heijden

2
Rzeczywista realizacja. Powodem, dla którego zdecydowałem się to opublikować, jest to, że spędzam dużo czasu na szukaniu tego samego problemu i dodawaniu do siebie elementów z różnych postów, aby go zrealizować. Komuś powinno być znacznie łatwiej zrobić to samo, mając ten post jako porównanie.
Stefanos Kargas

Pomogło mi pokazanie ustawienia allowCredentials = "true"w @CrossOriginadnotacji.
ponder275

@lennylip wspomniany w swojej odpowiedzi powyżej, pokazuje błąd dla tej samej witryny i bezpiecznej flagi. Jak to osiągnąć z serwerem localhost bez bezpiecznej flagi.
nirmal patel

2

W przypadku wersji ekspresowej zaktualizuj bibliotekę Express do 4.17.1najnowszej stabilnej wersji. Następnie;

W CorsOption: Zestaw origindo adresu URL lub adresu URL localhost produkcji frontend i credentialsna true przykład

  const corsOptions = {
    origin: config.get("origin"),
    credentials: true,
  };

Moje pochodzenie ustawiam dynamicznie za pomocą modułu config npm .

Następnie w res.cookie:

Dla localhost: nie musisz w ogóle ustawiać tej samej witryny i bezpiecznej opcji, możesz ustawić httpOnly , aby truena http cookies do ataku Zapobieganie XSS i innych przydatnych opcji , w zależności od przypadku użycia.

W środowisku produkcyjnym należy ustawić sameSitena noneżądanie między źródłami i securenatrue . Pamiętaj, że sameSitedziała z ekspresową najnowszą wersją tylko teraz, a najnowsza wersja Chrome ustawia tylko pliki cookie https, co wymaga bezpiecznej opcji.

Oto jak uczyniłem mój dynamiczny

 res
    .cookie("access_token", token, {
      httpOnly: true,
      sameSite: app.get("env") === "development" ? true : "none",
      secure: app.get("env") === "development" ? false : true,
    })

0

Odpowiedź Pima jest bardzo pomocna. W moim przypadku muszę użyć

Expires / Max-Age: "Session"

Jeśli jest to data i godzina, nawet jeśli nie wygasła, nadal nie wyśle ​​pliku cookie do zaplecza:

Expires / Max-Age: "Thu, 21 May 2020 09:00:34 GMT"

Mam nadzieję, że będzie to pomocne dla przyszłych osób, które mogą napotkać ten sam problem.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.