# теория · шаг 3 из 7
auth_request: решает внешний сервис
Коротко
auth_request отдаёт решение «пускать или нет» внешнему сервису: nginx делает
подзапрос и смотрит только на код ответа.
2xxпропускает,401и403отдаются клиенту как есть, всё остальное - 500. Замер: сервис ответил 302 - клиент получил 500.- Тело ответа сервиса не используется вовсе: решает код.
- Блок проверки закрывают
internal;- иначе он торчит наружу. Замер: с ним прямой запрос даёт 404, без него 200. - Подзапрос идёт на каждый запрос, поэтому сервис обязан быть быстрым.
Если знаешь таблицу кодов и помнишь про internal - листай до «Теперь сам».
Сначала ответь сам
Сервис авторизации отвечает 302 - «иди логиниться». Что увидит пользователь?
клиент получил 500
Пятисотку. Для auth_request осмысленных ответов ровно три: 2xx, 401 и
403. Всё остальное - включая редирект, таймаут и полное молчание - считается
поломкой проверяющего.
Замер по всем кодам сразу:
| сервис ответил | клиент получил |
|---|---|
| 200 | 200 |
| 204 | 200 |
| 401 | 401 |
| 403 | 403 |
| 302 | 500 |
| 500 | 500 |
Строка с 302 - самая частая ошибка при подключении: разработчик сервиса пишет привычный редирект на форму входа, и вместо страницы логина пользователь видит ошибку сервера.
На что это похоже
Вахтёр звонит в отдел кадров: «Иванов пришёл, пускать?» Ему нужен ответ «да», «нет» или «нет, но пусть представится». Ответ «а пусть он сходит в третий кабинет» вахтёра не устраивает - он не умеет провожать людей по кабинетам, и единственное, что он может, - развести руками.
Механизм: подзапрос на каждый запрос
Правило
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
с 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 на всём защищённом участке.
Комментарии
Пока нет комментариев. Будь первым!
Оставить комментарий