Установите файлы 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

LennyLip
источник
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,
    })
Абдулла Оладипо
источник