Установите файлы cookie для запросов с перекрестным происхождением


97

Как делиться файлами cookie из разных источников? В частности, как использовать Set-Cookieзаголовок в сочетании с заголовком Access-Control-Allow-Origin?

Вот объяснение моей ситуации:

Я пытаюсь установить файл cookie для API, который работает localhost:4000в веб-приложении, на котором размещено localhost:3000.

Кажется, я получаю правильные заголовки ответов в браузере, но, к сожалению, они не действуют. Это заголовки ответа:

HTTP / 1.1 200 ОК
Доступ-Контроль-Разрешить-Происхождение: http: // localhost: 3000
Варьируется: происхождение, кодирование-принятие
Set-Cookie: token = 0d522ba17e130d6d19eb9c25b7ac58387b798639f81ffe75bd449afbc3cc715d6b038e426adeac3316f0511dc7fae3f7; Макс-возраст = 86400; Домен = localhost: 4000; Путь = /; Срок действия истекает = Вт, 19 сентября 2017 г., 21:11:36 GMT; HttpOnly
Тип содержимого: приложение / json; charset = utf-8
Длина содержимого: 180
ETag: W / "b4-VNrmF4xNeHGeLrGehNZTQNwAaUQ"
Дата: пн, 18 сентября 2017 г., 21:11:36 GMT
Подключение: keep-alive

Кроме того, я вижу файл cookie, Response Cookiesкогда проверяю трафик на вкладке «Сеть» в инструментах разработчика Chrome. Тем не менее, я не вижу, чтобы файл cookie был установлен на вкладке «Приложение» ниже Storage/Cookies. Я не вижу ошибок CORS, поэтому полагаю, что мне не хватает чего-то еще.

Какие-либо предложения?

Обновление I:

Я использую модуль запроса в приложении React-Redux для отправки запроса /signinконечной точке на сервере. Для сервера использую экспресс.

Экспресс-сервер:

res.cookie ('токен', 'xxx-xxx-xxx', {maxAge: 86400000, httpOnly: true, domain: 'localhost: 3000'})

Запрос в браузере:

request.post ({uri: '/ signin', json: {userName: 'userOne', пароль: '123456'}}, (err, response, body) => {
    // делаем что-то
})

Обновление II:

Я как сумасшедший настраиваю заголовки запроса и ответа, убеждаясь, что они присутствуют как в запросе, так и в ответе. Ниже скриншот. Обратите внимание на заголовки Access-Control-Allow-Credentials, Access-Control-Allow-Headers, Access-Control-Allow-Methodsи Access-Control-Allow-Origin. Глядя на проблему, которую я обнаружил на github Axios , у меня сложилось впечатление, что все необходимые заголовки теперь установлены. Но все равно не повезло ...

введите описание изображения здесь


4
@PimHeijden взгляните на это: developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/ ... может быть, вам нужно использовать withCredentials?
Kalamarico

2
Хорошо, вы используете запрос, и я думаю, что это не лучший выбор, взгляните на этот пост и ответ, аксиомы, я думаю, могут быть вам полезны. stackoverflow.com/questions/39794895/…
Kalamarico

Благодарность! Я не заметил, что requestмодуль не предназначен для использования в браузере. Axios, похоже, пока отлично справляется. Теперь я получаю и заголовок: Access-Control-Allow-Credentials:trueи Access-Control-Allow-Origin:http://localhost:3000(используется для включения CORS). Это кажется правильным, но Set-Cookieзаголовок ничего не делает ...
Пим Хейден

Та же проблема, но с прямым использованием Axios: stackoverflow.com/q/43002444/488666 . Хотя { withCredentials: true }это действительно требуется для стороны Axios, заголовки серверов также должны быть тщательно проверены (см. Stackoverflow.com/a/48231372/488666 )
Frosty Z

какие заголовки сервера?
Пим Хейден

Ответы:


155

Что тебе необходимо сделать

Чтобы разрешить прием и отправку файлов cookie по запросу CORS, выполните следующие действия.

Back-end (сервер): установите Access-Control-Allow-Credentialsзначение заголовка HTTP равным true. Кроме того, убедитесь, что заголовки HTTP Access-Control-Allow-Originи Access-Control-Allow-Headersустановлены без подстановочного знака* .

Для получения дополнительной информации о настройке CORS в express js прочтите документы здесь

Внешний интерфейс (клиент): установите этот XMLHttpRequest.withCredentialsфлаг true, это может быть достигнуто разными способами в зависимости от используемой библиотеки запроса-ответа:

Или

Избегайте использования CORS в сочетании с файлами cookie. Вы можете добиться этого с помощью прокси.

Если вы по какой-то причине не избегаете этого. Решение выше.

Оказалось, что Chrome не будет устанавливать cookie, если домен содержит порт. Настроить его на localhost(без порта) не проблема. Большое спасибо Эрвину за этот совет!


2
Я думаю, у вас есть эта проблема только из-за localhostпроверки здесь: stackoverflow.com/a/1188145, а также это может помочь вашему делу ( stackoverflow.com/questions/50966861/… )
Эдвин

5
Этот ответ мне очень помог! Потребовалось много времени, чтобы его найти. Но я думаю, что в ответе следует упомянуть, что также требуется установка Access-Control-Allow-Originявного домена, а не просто "*". Тогда это был бы идеальный ответ
e.dan

6
это хороший ответ, и вся настройка для CORS, заголовков, бэкэнда и внешнего интерфейса, а также избегание localhost с переопределением / etc / hosts локально с реальным поддоменом, но я вижу, что почтальон показывает SET-COOKIE в заголовках ответов, но хромированная отладка не показать это в заголовках ответов, а также файл cookie на самом деле не установлен в Chrome. Есть еще идеи для проверки?
bjm88

1
@ bjm88 Ты в конце концов понял это? Я в точно такой же ситуации. Файл cookie устанавливается правильно при подключении с localhost: 3010 к localhost: 5001, но не работает с localhost: 3010 на fakeremote: 5001 (что указывает на 127.0.0.1 в моем файле hosts). То же самое, когда я размещаю свой сервер на реальном сервере с персональным доменом (подключаюсь с localhost: 3010 к mydomain.com). Я сделал все, что рекомендовано в этом ответе, и пробовал много других вещей.
Форма

1
В Angular обязательное изменение на стороне клиента также заключается в добавлении withCredentials: true к параметрам, передаваемым в HttpClient.post, .get, options и т. Д.
Марвин

11

Примечание для браузера Chrome, выпущенного в 2020 году.

В будущих версиях Chrome файлы cookie будут доставляться с межсайтовыми запросами, только если они установлены с помощью SameSite=Noneи Secure.

Поэтому, если ваш внутренний сервер не устанавливает SameSite = None, Chrome будет использовать SameSite = Lax по умолчанию и не будет использовать этот файл cookie с запросами {withCredentials: true}.

Подробнее https://www.chromium.org/updates/same-site .

Разработчики Firefox и Edge также хотят выпустить эту функцию в будущем.

Здесь указаны спецификации: https://tools.ietf.org/html/draft-west-cookie-incrementalism-01#page-8


2
предоставление samesite = none и флаг безопасности требуют HTTPS. Как добиться этого в локальной системе, где HTTPS не подходит? мы можем как-то обойти?
Нирмал Патель

@nirmalpatel Просто удалите значение "Lax" в консоли разработчика Chome.
LennyLip

3

Для того, чтобы клиент мог читать файлы cookie из запросов из разных источников, вам необходимо иметь:

  1. Все ответы от сервера должны содержать в заголовке следующее:

    Access-Control-Allow-Credentials: true

  2. Клиент должен отправлять все запросы с withCredentials: trueопцией

В моей реализации с Angular 7 и Spring Boot я добился этого с помощью следующего:


На стороне сервера:

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

origins = "http://my-cross-origin-url.com"Часть будет добавить Access-Control-Allow-Origin: http://my-cross-origin-url.comв заголовок ответа каждого сервера

Эта allowCredentials = "true"часть будет добавляться Access-Control-Allow-Credentials: trueк заголовку ответа каждого сервера, что нам нужно, чтобы клиент мог читать файлы cookie.


Сторона клиента:

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);
    }
}

С помощью этого класса вы фактически добавляете дополнительный материал во все свои запросы.

Первая часть req = req.clone({ withCredentials: true });- это то, что вам нужно для отправки каждого запроса с withCredentials: trueопцией. Практически это означает, что запрос OPTION будет отправлен первым, так что вы получите свои файлы cookie и токен авторизации среди них, прежде чем отправлять фактические запросы POST / PUT / DELETE, которым этот токен должен быть прикреплен к ним (в заголовке) в приказ серверу проверить и выполнить запрос.

Вторая часть - это та, которая специально обрабатывает токен анти-CSRF для всех запросов. При необходимости считывает его из файла cookie и записывает в заголовок каждого запроса.

Желаемый результат выглядит примерно так:

ответ запрос


что этот ответ добавляет к существующему?
Пим Хейден,

1
Реальная реализация. Причина, по которой я решил опубликовать это, заключается в том, что я трачу много времени на поиск одной и той же проблемы и складываю части из разных сообщений, чтобы понять ее. Кому-то должно быть намного проще сделать то же самое, имея этот пост для сравнения.
Стефанос Каргас,

Мне помогло отображение настройки allowCredentials = "true"в @CrossOriginаннотации.
ponder275

@lennylip, упомянутый в его ответе выше, показывает ошибку для Samesite и безопасного флага. Как добиться этого с помощью сервера localhost без флага безопасности.
Нирмал Патель

0

Ответ Пима очень полезен. В моем случае я должен использовать

Expires / Max-Age: "Session"

Если это dateTime, даже если срок его действия не истек, он все равно не отправит cookie в бэкэнд:

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

Надеюсь, это будет полезно для будущих людей, которые могут столкнуться с той же проблемой.


0

Для экспресс-обновления обновите свою экспресс-библиотеку до 4.17.1последней стабильной версии. Затем;

В CorsOption: Установить originна свой локальный URL или производственной интерфейс URL и credentialsк true примеру

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

Я устанавливаю свое происхождение динамически, используя модуль config npm .

Затем в res.cookie:

Для локального хоста: вам не нужно устанавливать sameSite и безопасный вариант на всех, вы можете установить , httpOnlyчтобы trueдля HTTP куки , чтобы предотвратить XSS атаку и другие полезные опции в зависимости от вашего использования.

Для производственной среды, вам необходимо установить sameSiteдля noneзапроса кросс-происхождения и secureк true. Помните, sameSiteработает только с последней версией Express, а последняя версия Chrome устанавливает только cookie поверхhttps , поэтому требуется безопасный вариант.

Вот как я сделал свой динамичный

 res
    .cookie("access_token", token, {
      httpOnly: true,
      sameSite: app.get("env") === "development" ? true : "none",
      secure: app.get("env") === "development" ? false : true,
    })
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.