1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7

# теория · шаг 3 из 7

auth_request: решает внешний сервис

Коротко

auth_request отдаёт решение «пускать или нет» внешнему сервису: nginx делает подзапрос и смотрит только на код ответа.

  • 2xx пропускает, 401 и 403 отдаются клиенту как есть, всё остальное - 500. Замер: сервис ответил 302 - клиент получил 500.
  • Тело ответа сервиса не используется вовсе: решает код.
  • Блок проверки закрывают internal; - иначе он торчит наружу. Замер: с ним прямой запрос даёт 404, без него 200.
  • Подзапрос идёт на каждый запрос, поэтому сервис обязан быть быстрым.

Если знаешь таблицу кодов и помнишь про internal - листай до «Теперь сам».

Сначала ответь сам

Сервис авторизации отвечает 302 - «иди логиниться». Что увидит пользователь?

↳  выводсервис ответил 302
клиент получил 500

Пятисотку. Для auth_request осмысленных ответов ровно три: 2xx, 401 и 403. Всё остальное - включая редирект, таймаут и полное молчание - считается поломкой проверяющего.

Замер по всем кодам сразу:

сервис ответил клиент получил
200 200
204 200
401 401
403 403
302 500
500 500

Строка с 302 - самая частая ошибка при подключении: разработчик сервиса пишет привычный редирект на форму входа, и вместо страницы логина пользователь видит ошибку сервера.

На что это похоже

Вахтёр звонит в отдел кадров: «Иванов пришёл, пускать?» Ему нужен ответ «да», «нет» или «нет, но пусть представится». Ответ «а пусть он сходит в третий кабинет» вахтёра не устраивает - он не умеет провожать людей по кабинетам, и единственное, что он может, - развести руками.

Механизм: подзапрос на каждый запрос

клиент nginx 1. сервис авторизации 2. приложение подзапрос идёт без тела, но с заголовками клиента ответ сервиса используется только кодом - тело отбрасывается и всё это на КАЖДЫЙ запрос, включая картинки
Проверка идёт до основного запроса. Медленный сервис делает медленным весь сайт.

Правило

/etc/nginx/conf.d/shop.local.confhttp server
location /private/ {
    auth_request /auth;
    auth_request_set $user $upstream_http_x_user;
    proxy_set_header X-User $user;
    proxy_pass http://app:8000;
}

location = /auth {
    internal;
    proxy_pass              http://authsvc:9000/verify;
    proxy_pass_request_body off;
    proxy_set_header        Content-Length "";
    proxy_set_header        X-Original-URI $request_uri;
}

Четыре служебные строки в блоке проверки, и каждая нужна:

  • internal; - блок доступен только для подзапросов;
  • proxy_pass_request_body off; плюс пустой Content-Length - тело запроса сервису не нужно, а тащить его на каждую проверку дорого;
  • X-Original-URI - сервис должен знать, к чему именно просят доступ.

auth_request_set вытаскивает данные из ответа сервиса в переменную. Имя $upstream_http_x_user собрано по правилу «$upstream_http_ плюс имя заголовка ответа»: здесь это заголовок X-User, который вернул сервис. Так имя пользователя доезжает до приложения, не заставляя его повторно разбирать cookie.

Разбор: забытый internal

↳  выводпрямой запрос к /auth снаружи
с internal:  404
без него:    200

Без internal блок проверки становится обычным публичным адресом. Последствия зависят от сервиса: как минимум это точка, по которой можно перебирать токены, не трогая основное приложение; как максимум - сервис отдаёт в заголовках имя пользователя и его права, и всё это доступно любому.

Проверяется одной командой, и делать это надо сразу после настройки:

$  команда
curl -s -o /dev/null -w '%{http_code}\n' https://shop.local/auth

404 - хорошо. Что угодно другое - блок открыт.

Разбор: цена на каждый запрос

Подзапрос идёт на каждый запрос в защищённом location, включая картинки, css и favicon. Отсюда три следствия:

  • сервис авторизации должен отвечать за единицы миллисекунд, иначе он станет самым медленным местом сайта;
  • он должен переживать нагрузку основного сайта целиком - и его падение превращает сайт в сплошные 500;
  • статику разумно выносить из-под проверки в отдельный location, если она не секретная.

Смягчить это помогает кеш решений на стороне сервиса или proxy_cache на самом подзапросе - но кешировать решение по авторизации надо очень осознанно, ключом, включающим cookie или токен.

Что ломается без этого

Сервис молчит - сайт лежит. Таймаут подзапроса даёт 500 клиенту. Это поведение по умолчанию и оно правильное (пускать всех при недоступной проверке хуже), но означает, что сервис авторизации становится критической зависимостью наравне с бэкендом.

Ответ 302 вместо 401. Разобрано выше: самая частая ошибка на стыке. Правило для авторов сервиса простое: подзапросу отвечают кодом и ничем больше, а редирект на форму входа делает фронтенд, получив 401.

Заголовки от сервиса не доезжают сами. Ответ подзапроса отбрасывается целиком; всё, что нужно передать дальше, вытаскивают через auth_request_set и подставляют явным proxy_set_header. Забыли - приложение не узнает, кто пришёл.

Зачем это в работе

auth_request - способ поставить единую авторизацию перед несколькими приложениями, не переписывая их. Типовые применения: единый вход для набора внутренних панелей, проверка подписи у статики, ограничение доступа к чужому сервису, который сам ничего не умеет.

Соседний вариант того же класса - secure_linkРазбирается в разделе 12, глава «Доступ: allow, deny, auth_basic, auth_request»: Подписанные ссылки и защита от хотлинка из отдельного урока: там решение принимает не сервис, а подпись в самой ссылке. Разница в том, что подпись не требует запроса вовсе, но и отозвать её нельзя.

Теперь сам

Подключили auth_request, всё работает. Через неделю сайт начал отдавать 500 по воскресеньям утром. Логи nginx показывают 500 на всех защищённых маршрутах, логи приложения чисты. Куда смотреть?

В сервис авторизации: по воскресеньям у него, скорее всего, идёт обслуживание или перезапуск. Для nginx недоступность проверяющего - это 500 на каждый запрос, и приложение при этом даже не вызывается, поэтому в его логах пусто. Проверяется за минуту: curl к сервису напрямую в момент проблемы. Смягчается это мониторингом самого сервиса и решением, что делать при его отказе, - вплоть до осознанного error_page 500 = @readonly (при коде 500 обслужить именованным блоком @readonly) с ограниченным режимом.

Главное

auth_request делает подзапрос к сервису и смотрит только на код: 2xx пропускает, 401 и 403 отдаёт клиенту, всё остальное превращается в 500 - включая привычный редирект 302, и это самая частая ошибка при подключении. Блок проверки закрывают internal;, иначе он доступен снаружи (замер: 404 против 200). Данные из ответа вытаскивают auth_request_set и передают явным заголовком. Подзапрос идёт на каждый запрос, поэтому сервис обязан быть быстрым и живым: его недоступность - это 500 на всём защищённом участке.

Комментарии

Пока нет комментариев. Будь первым!

Оставить комментарий

Комментарий появится после проверки. Email не публикуется. Войти, чтобы не вводить имя каждый раз.