Веб-основы для тестировщика
Я открыл страницу и попросил её сходить за данными на соседний сервис. Из браузера пришла ошибка «Failed to fetch», данные не получены. Тот же самый адрес, запрошенный не из браузера, спокойно вернул 200 и тело с балансом. Сервер отдаёт данные всегда - блокирует их браузер.
Стержень: ошибку CORS накладывает браузер, поэтому её нельзя воспроизвести консольным клиентом, а токен закодирован, но не зашифрован.
// Формулировки: «что происходит при вводе адреса?», «почему CORS не воспроизводится через curl?», «зачем куке HttpOnly?».
Путь запроса и где он рвётся
Запрос проходит четыре понятных шага. Первый - DNS: имя api.example.com превращается в числовой адрес машины. Второй - TCP: между твоим клиентом и этой машиной устанавливается соединение. Третий - TLS: соединение шифруется, стороны сверяют сертификат. Четвёртый - собственно HTTP-запрос и ответ приложения.
Зачем тестировщику эта лестница: она даёт порядок диагностики. «Не работает» может означать «имя не резолвится», «соединение не устанавливается», «сертификат просрочен» и «приложение вернуло 500» - это четыре разные команды и четыре разных ответственных. Разбирают снизу вверх, и до кода доходят последним.
// Сам HTTP-запрос устроен просто: метод, путь, параметры после вопросительного знака, заголовки (служебные пары «имя: значение») и тело. Всё это видно во вкладке Network панели разработчика в браузере - там же видно, что ушло и что вернулось, вплоть до байта.
- DNS
- превращение имени сайта в числовой адрес машины
- заголовки
- служебные пары «имя: значение» рядом с запросом и ответом
CORS: замер в настоящем браузере
Вернусь к опыту из начала. Источник (origin) - это сочетание протокола, домена и порта. Страница с одного источника, сервис на другом - для браузера это чужой источник, даже если оба на твоей машине и различаются только портом.
CORS (cross-origin resource sharing) - правило браузера: прежде чем отдать странице ответ от чужого источника, он проверяет, разрешил ли тот сервис такое обращение специальным заголовком. Замер: сервис без заголовка - страница получила «Failed to fetch»; тот же сервис с заголовком разрешения - страница получила {"balance":105000}; тот же адрес запросом не из браузера - 200 и те же данные.
// Отсюда вывод, который экономит дни. Воспроизвести ошибку CORS через curl или Postman нельзя. Это программы, которые шлют запросы к серверу напрямую, без страницы: они не браузеры и правило источников не применяют. Поэтому фразу разработчика «у меня в Postman работает» нельзя принимать как аргумент - Postman тут ничего не проверяет. Разбирать надо в браузере, глядя на заголовки ответа и на предварительный запрос OPTIONS, который браузер шлёт перед непростыми обращениями.
- источник (origin)
- протокол плюс домен плюс порт; граница правил браузера
- CORS
- cross-origin resource sharing: правило браузера о доступе к чужому источнику
Куки и токены: что видно снаружи
Кука - маленькая пара «имя-значение», которую сервер просит браузер запомнить и присылать обратно. Замер: сервис поставил две куки - session с признаком HttpOnly и theme без него. Скрипт на странице, запросив список кук, увидел только theme=dark. Сессионная кука для него невидима - и ровно это спасает при XSS (cross-site scripting), когда на страницу попадает чужой скрипт: украсть сессию через него не выйдет.
Остальные признаки куки тоже проверяют: Secure (передаётся только по шифрованному соединению), SameSite (не отправлять её при переходах с чужих сайтов - защита от CSRF (cross-site request forgery), когда чужая страница дёргает твой сервис от твоего имени), срок жизни, домен и путь.
// Второй способ носить личность - токен в заголовке. Я собрал такой токен и разобрал его: середина читается обычным декодированием без всякого ключа - там оказалось {"sub":"user-42","role":"user","email":"anna@mail.ru"}. При этом подменить роль на admin не вышло: подпись перестала сходиться. Вывод точный - подделать нельзя, а прочитать может кто угодно, поэтому в JWT (JSON Web Token) не кладут ничего, что нельзя показывать.
- HttpOnly
- признак куки: скрипту страницы она не видна
- JWT
- JSON Web Token: подписанный токен; закодирован, но не зашифрован
Как отвечать: «Почему ошибка CORS видна только в браузере?»
Потому что это правило самого браузера, а не запрет сервера. Работает оно так: когда скрипт на странице одного источника просит данные у другого источника, браузер перед тем, как отдать странице ответ, проверяет, разрешил ли тот сервис такое обращение ответным заголовком. Если разрешения нет - браузер сам блокирует чтение и показывает ошибку, хотя сервер запрос вполне мог обработать. Я проверял ровно этот сценарий: страница получила «Failed to fetch», а тот же самый адрес, запрошенный не из браузера, вернул двести и полные данные. Отсюда практический вывод: воспроизвести CORS через curl или Postman невозможно, они не браузеры и правило источников не применяют, у них запрос проходит всегда. Поэтому разбирать надо в браузере: смотреть предварительный запрос OPTIONS и заголовки разрешения в ответе. И когда разработчик говорит «у меня в Postman всё работает» - это не аргумент, Postman в этом месте вообще ничего не проверяет.
Почему это сильный ответ: назван корень (правило браузера, не сервера), объяснён механизм, приведено наблюдение с двумя разными исходами и сделан практический вывод про инструменты.
На чём валят
- −Разбирать CORS через curl: правило накладывает браузер, консольный клиент его не применяет.
- −Класть личные данные в токен «он же закодирован» - середина читается без ключа.
- −Сессионная кука без HttpOnly: чужой скрипт на странице заберёт её вместе с сессией.
- −Проверять выход из аккаунта только по интерфейсу - токен может остаться живым на сервере.
- −Забыть про кэш: «починили, а у пользователя по-старому» - ответ приехал из сохранённой копии.
Проверьте себя
Пять вопросов из банка по этой подтеме. Всего их 11, остальные разбираются в тренажёре.
- Что делает DNS при обращении к сайту?A)Шифрует соединение между браузером и сервером для защиты передаваемых данныхB)Переводит доменное имя (site.ru) в IP-адрес сервераC)Хранит на сервере сессии пользователей, чтобы сайт помнил их между запросамиD)Распределяет входящие запросы между несколькими серверами для баланса нагрузки
показать ответ и разбор
+B)Переводит доменное имя (site.ru) в IP-адрес сервера// разбор: Компьютеры находят друг друга по IP-адресам, а люди помнят имена. DNS (система доменных имён) — распределённый справочник, который по доменному имени возвращает соответствующий IP. Браузер сначала спрашивает у DNS адрес site.ru, получает, например, 93.184.216.34, и только потом устанавливает TCP-соединение и шлёт HTTP-запрос. Для тестировщика это объясняет класс проблем: сайт «не открывается» из-за DNS (неверная запись, кэш), хотя сам сервер жив.
- Как cookie и сессии дают HTTP «память» о пользователе?A)Cookie хранит на клиенте пароль пользователя и шлёт его серверу при каждом запросеB)Сессии хранятся в DNS-записи домена, откуда сервер и подтягивает данные пользователяC)Сервер выдаёт cookie с идентификатором сессииD)Сервер запоминает пользователя по его IP-адресу, поэтому cookie для этого не нужны
показать ответ и разбор
+C)Сервер выдаёт cookie с идентификатором сессии// разбор: HTTP сам по себе не помнит предыдущие запросы. Чтобы связать их в диалог, при логине сервер создаёт сессию и отдаёт клиенту cookie с её идентификатором (Set-Cookie). Браузер автоматически прикладывает эту cookie к каждому последующему запросу к домену, и сервер по id находит сессию — так пользователь остаётся «залогинен». Для тестировщика это точка проверок: живёт ли сессия, истекает ли по таймауту, привязана ли к пользователю, безопасны ли флаги cookie (HttpOnly, Secure).
- Почему HTTP называют протоколом без состояния (stateless)?A)HTTP не хранит состояние, потому что данные шифруются и стираются после ответаB)Протокол помнит все прошлые запросы клиента и накапливает их в памяти сервераC)Stateless означает, что сервер отвечает без тела, одним лишь кодом статуса ответаD)Каждый запрос независим и не опирается на предыдущие
показать ответ и разбор
+D)Каждый запрос независим и не опирается на предыдущие// разбор: Stateless означает, что сервер не обязан помнить контекст между запросами: каждый HTTP-запрос самодостаточен и обрабатывается независимо от прошлых. Это упрощает масштабирование — любой запрос может уйти на любой сервер за балансировщиком. «Память» о пользователе и сессии организуют поверх протокола: клиент прикладывает cookie/токен, сервер по нему поднимает состояние из хранилища. Для тестирования важно: нельзя полагаться, что второй запрос «помнит» первый сам по себе — нужное состояние надо передавать явно.
- Что даёт использование HTTPS вместо HTTP?A)Шифрует трафик между клиентом и сервером (TLS)B)Ускоряет загрузку страниц за счёт сжатия трафика между клиентом и серверомC)Хранит cookie пользователя на сервере, освобождая память браузера клиентаD)Позволяет серверу самому инициировать соединение с клиентом в обход браузера
показать ответ и разбор
+A)Шифрует трафик между клиентом и сервером (TLS)// разбор: HTTPS — это HTTP поверх TLS. Он решает три задачи: конфиденциальность (трафик зашифрован, посредник не прочтёт пароли и данные), целостность (подмену по дороге видно) и аутентичность сервера (сертификат подтверждает, что это настоящий site.ru, а не подделка). По HTTP всё идёт открытым текстом и легко перехватывается в общей сети. Для тестировщика: проверяют, что чувствительные эндпоинты работают по HTTPS, сертификат валиден, а редирект с HTTP на HTTPS настроен.
- Что такое CORS и зачем он нужен?A)CORS шифрует кросс-доменные запросы, защищая передаваемые между сайтами данныеB)Правило браузера: скрипт с одного домена не обращается к API другого без явного разрешения сервера (заголовки)C)CORS ускоряет запросы к чужому API за счёт кэширования ответов в браузереD)CORS применяется на сервере и блокирует запросы ещё до их отправки клиентом
показать ответ и разбор
+B)Правило браузера: скрипт с одного домена не обращается к API другого без явного разрешения сервера (заголовки)// разбор: CORS (Cross-Origin Resource Sharing) — механизм безопасности браузера. По умолчанию действует same-origin policy: JS-код со страницы domainA не может читать ответы API на domainB. CORS позволяет серверу B явно разрешить это, вернув заголовки (Access-Control-Allow-Origin и др.); при «сложных» запросах браузер сперва шлёт preflight OPTIONS. Важный нюанс для тестировщика: CORS проверяет именно браузер — из Postman или бэкенда запрос пройдёт и без него. Поэтому баг «работает в Postman, но не в браузере» часто про CORS.
дальше
Теорию прочитали. Навык ставится повторением
В Сеньорчике эта подтема идёт в ежедневных сессиях: движок возвращает её, пока ответы не станут уверенными, и ведёт прогресс отдельно по каждой подтеме. Теория внутри тоже бесплатна, лимит только на количество вопросов в день.