JavaRush /Курсы /Spring Security /authorizeHttpRequests...

authorizeHttpRequests: базовые правила

Spring Security
5 уровень , 1 лекция
Открыта

1. authorizeHttpRequests: правила вместо if-ов

Если вы когда-нибудь пытались «защитить API по-быстрому», то почти наверняка приходили к чему-то вроде if(user == null) return 401; прямо в контроллере. Первые пять минут это даже работает — пока не появляется второй контроллер, третий endpoint, «админка», «публичная зона», а потом ещё и внезапная потребность повторять одни и те же правила в нескольких местах. В итоге security превращается в слой хаоса.

Spring Security предлагает другой способ думать о доступе. Вместо того чтобы писать «проверки» в коде бизнес-слоя, мы описываем политику доступа к запросам отдельным декларативным языком. По‑человечески это звучит как короткие фразы: «всё требует входа», «публичное можно всем», «личное — только тем, кто вошёл». Именно здесь появляется authorizeHttpRequests.

Важно поймать идею: authorizeHttpRequests — это не «магическая кнопка безопасности», а способ превратить доступ в набор правил, который можно прочитать глазами сверху вниз. А если правило читается, его намного сложнее сломать случайной правкой (хотя… программисты — народ творческий, так что шанс остаётся).

2. authorizeHttpRequests(...): где живут правила и как их читать

Когда вы впервые видите authorizeHttpRequests, мозг может отреагировать примерно так: «Ага, ещё одна цепочка методов, сейчас будет боль». Но если убрать пафос, это просто блок конфигурации: «вот здесь мы описываем, кто имеет право выполнять HTTP-запросы». Это про authorization, а не про то, как именно пользователь входит в систему и откуда берётся пароль.

Вживую это выглядит так: у нас есть SecurityConfig, внутри — SecurityFilterChain, и мы добавляем одну строку, которая включает режим «правила доступа задаём мы». Дальше внутри лямбды auth -> ... будем писать сами правила.

Минимальный рабочий фрагмент, который уже задаёт смысл (и который удобно держать как стартовую точку), выглядит так:

// Внутри уже собранного SecurityFilterChain меняется вот этот блок:
http.authorizeHttpRequests(auth -> auth
    // Правило "по умолчанию": всё должно требовать входа
    .anyRequest().authenticated()
);

Сейчас не цепляйтесь к anyRequest() и authenticated() — мы подробно разберём их прямо в этой лекции. Пока важнее увидеть общую форму: authorizeHttpRequests принимает лямбду, внутри которой мы складываем правила. И дальше вам нужно научиться буквально проговаривать это как предложение: «любой запрос должен быть аутентифицирован».

Чтобы закрепить, полезно держать маленькую «шпаргалку-переводчик»:

Фрагмент DSL Как это читать по‑человечески Про что это
authorizeHttpRequests(...) “Настраиваем правила доступа к запросам” Authorization
anyRequest() “Всё, что осталось” Матчинг запросов
permitAll() “Пустить всех” Разрешение без условий
authenticated() “Пустить только вошедших” Требование аутентификации

В следующих лекциях этого дня появятся более точные правила для конкретных URL, но уже сейчас вы должны уверенно понимать базовый язык: «какой запрос» → «какое условие».

3. Базовые правила доступа

anyRequest() — ловим «всё остальное» одним правилом

Когда мы описываем правила доступа, сразу возникает вопрос: а что делать с запросами, которые мы явно не перечислили? Их можно игнорировать, надеяться на чудо, поставить свечку за стабильность продакшена — но лучше сделать профессионально: определить общее правило, которое срабатывает на всё, что не подошло под более точные условия. Это и есть anyRequest().

anyRequest() читается очень буквально: «любой запрос». На практике это означает «любой запрос, для которого не нашлось более конкретного правила выше». Даже если сегодня у вас правил всего одно, привычка думать «а чем заканчивается политика?» — это уже половина качества security-конфигурации.

Есть ещё одна важная мысль: anyRequest() по смыслу должно быть последним. Это как фраза «вообще всем» — если вы произнесли её первой, дальше уже бессмысленно уточнять. В Spring Security это не просто философия: anyRequest() фактически завершает построение набора правил, и попытки добавить что-то «после» обычно заканчиваются ошибкой конфигурации или недостижимыми правилами.

Простейший пример, который задаёт нижнюю границу безопасности, выглядит так:

import org.springframework.security.config.annotation.web.builders.HttpSecurity;

// Настраиваем правила авторизации запросов
http.authorizeHttpRequests(auth -> auth
    // "Любой запрос" (который не совпал с более конкретными правилами) должен быть от аутентифицированного пользователя
    .anyRequest().authenticated()
);

Смысл простой: если мы забыли что-то описать отдельно, оно всё равно останется закрытым для анонимных. Это очень близко к принципу secure-by-default, который мы обсуждали ранее: лучше случайно закрыть лишнее (и потом осознанно открыть), чем случайно открыть лишнее (и потом объяснять на ретроспективе, почему у вас /api/admin/users был публичным).

permitAll() — «публично» не значит «безопасно»

permitAll() — это самое простое правило, и именно поэтому с ним легко ошибиться. Оно означает: «разрешить доступ всем, независимо от того, вошёл пользователь или нет». Если вы читаете это как «разрешить всем аутентифицированным» — это не permitAll, это уже ближе к authenticated. Тут всё буквально: permit — «разрешить», all — «всем».

Звучит так, будто permitAll отключает Spring Security. На самом деле нет: Spring Security продолжает жить в приложении, фильтры продолжают отрабатывать, SecurityContext продолжает существовать (просто там будет anonymous пользователь, если никто не вошёл). permitAll — это именно решение на уровне авторизации: «не требуй никаких условий».

Самый «открытый» baseline выглядит так:

import org.springframework.security.config.annotation.web.builders.HttpSecurity;

// Настраиваем правила авторизации запросов
http.authorizeHttpRequests(auth -> auth
    // Полностью публичный режим: пускаем всех, даже anonymous
    .anyRequest().permitAll()
);

И это полезно как контрастный эксперимент. Например, вы хотите проверить, что endpoint вообще работает, и отделить проблему «у меня контроллер не поднялся» от проблемы «меня не пускает security». В таком режиме приложение снова становится «как будто без охраны».

Но важный профессиональный момент: permitAll на anyRequest() — это не решение, а максимум временная диагностическая позиция. В реальном бэкенд-проекте держать всё публичным — примерно как повесить на сервер табличку «пароль: admin». Возможно, честно, но лучше всё же не надо.

authenticated() — «вошёл в систему» без ролей и прав

Слово authenticated() звучит грозно, но по смыслу оно очень базовое: «пустить только те запросы, где пользователь аутентифицирован». То есть система должна понимать, кто делает запрос, и считать его не anonymous. При этом authenticated() не проверяет, какой именно это пользователь, не проверяет роль, не проверяет «можно ли ему публиковать статьи» и не проверяет «это его черновик или чужой». Это просто барьер «сначала представься».

Очень полезная аналогия (и простите за бытовую аналогию): authenticated() — это турникет по пропуску. Он проверяет, что пропуск есть, но не проверяет, в какую именно комнату вам можно. Для комнат будут другие механики, но сегодня мы учимся включать хотя бы турникет.

В коде это выглядит так:

import org.springframework.security.config.annotation.web.builders.HttpSecurity;

// Настраиваем правила авторизации запросов
http.authorizeHttpRequests(auth -> auth
    // Пускаем только запросы, где пользователь уже "не anonymous"
    .anyRequest().authenticated()
);

И вот здесь важно связать это с тем, что мы уже знаем из предыдущих дней. Spring Security обычно восстанавливает Authentication в SecurityContext. Если запрос anonymous, внутри часто будет AnonymousAuthenticationToken (мы это обсуждали в контексте SecurityContext). authenticated() как раз означает: «anonymous — не подходит, нужен настоящий пользователь».

Ещё один частый «подводный камень новичка»: authenticated() — это не «проверка логина и пароля в этой точке». Проверка логина/пароля происходит в механизме аутентификации (filters + AuthenticationManager + providers). А authenticated() — это уже про результат: «есть ли у запроса состояние “пользователь подтверждён”».

То есть сначала где-то ранее в цепочке (или раньше по времени) должна случиться аутентификация, и только потом authenticated() может сказать: «да, пользователь вошёл, пускаем». Если аутентификация не произошла, Spring Security отвечает отказом (и вы видите 401, редирект на логин и прочие радости, которые мы уже наблюдали на Day 2).

4. Два baseline: «всё закрыто» и «всё открыто»

На этом этапе полезно сделать то, что любят инженеры: взять два крайних режима и посмотреть, как в них ведёт себя система. Один режим — «по умолчанию всё закрыто для anonymous», второй — «по умолчанию всё открыто для всех». В реальном продукте оба редко бывают финальными, но для тренировки чтения правил они идеальны.

Давайте возьмём наш учебный проект Secure Content Platform API. У него есть зоны, которые по смыслу должны быть публичными (например, чтение опубликованных статей), и зоны, которые должны быть личными или привилегированными (например, /api/me, редакторские и admin-операции). Мы пока не умеем выбирать конкретные URL правилами — это будет следующая лекция. Но уже сейчас можем увидеть, как работает «сетка безопасности».

Чтобы эксперимент был нагляднее, добавим самый простой endpoint для проверки (если он у вас уже есть — отлично, просто используйте существующий). Например, маленький «пинг»:

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class PingController {

    // Простой endpoint для проверки: отвечает "pong", чтобы было видно, что запрос дошёл до контроллера
    @GetMapping("/api/ping")
    public String ping() {
        return "pong";
    }
}

Если каркас цепочки уже собран и базовые механизмы входа оставлены включёнными, для этого сравнения меняется только один кусок — сами правила доступа.

Теперь рассмотрим два варианта.

Вариант А: «всё требует входа».
Мы ставим общий барьер:

// Базовая политика: любой запрос требует аутентификации
http.authorizeHttpRequests(auth -> auth
    .anyRequest().authenticated()
);

В таком режиме любой запрос без аутентификации будет отвергнут. Чем именно — зависит от клиента, которым вы стучитесь (браузер, Postman, curl), и от того, какие механизмы аутентификации сейчас включены. По умолчанию Spring Security часто показывает логин-страницу в браузере и/или предлагает basic challenge клиентам. Но смысл один: «анонимно нельзя».

Если вы проверяете через curl, логика обычно выглядит так:

# Запрос без учётных данных — анонимный
curl -i http://localhost:8080/api/ping
# ожидаемо: 401 Unauthorized (или редирект на /login, если вы смотрите как браузер)

А если вы укажете учётные данные (например, дефолтного пользователя, которого Spring Boot создавал ранее), то запрос пройдёт:

# Запрос с basic auth — пользователь "представился"
curl -i -u user:password http://localhost:8080/api/ping
# HTTP/1.1 200
# pong

(Пароль, конечно, будет не password, а тот самый сгенерированный, который вы видели в логах при старте приложения ранее.)

Вариант Б: «всё публично».
Меняем одну строчку:

// Полностью публичная политика: любой запрос разрешён без условий
http.authorizeHttpRequests(auth -> auth
    .anyRequest().permitAll()
);

И теперь запрос без аутентификации спокойно проходит:

# Теперь даже анонимный запрос проходит, потому что политика permitAll
curl -i http://localhost:8080/api/ping
# HTTP/1.1 200
# pong

Почему оба варианта плохи как финал? Потому что проект по своей природе не однозонный. В нём одновременно есть публичное чтение и приватные/привилегированные действия. Если «всё закрыто», вы ломаете публичную часть и делаете платформу контента странной: «чтобы прочитать статью — войдите». Если «всё открыто», ломаете безопасность: «чтобы управлять пользователями — просто зайдите по ссылке».

И именно поэтому нам нужен язык, который умеет сказать: «вот этот кусок публичный, а вот этот — только для вошедших». Сегодня мы выучили слова permitAll() и authenticated(). В следующей лекции мы научимся применять эти слова к конкретным URL. Пока же важно не путать смысл этих операторов и уметь прогнозировать поведение приложения в крайних режимах.

5. Типичные ошибки при настройке правил доступа

Ошибка №1: путать permitAll() и «разрешить после входа».
Новичок иногда думает, что permitAll — это «пустить всех пользователей системы». Но на самом деле это «пустить вообще всех, включая anonymous». Это критично в проекте, где есть admin-endpoints. С такой путаницей можно случайно оставить привилегированную операцию публичной, и это будет не баг, а катастрофа (просто очень быстрая).

Ошибка №2: считать authenticated() проверкой роли.
authenticated() не знает ничего про роли ADMIN или EDITOR. Он проверяет только факт «пользователь не anonymous». Из-за этого иногда возникают ложные ожидания: «почему обычный пользователь прошёл туда, куда нельзя?» Ответ: потому что вы требовали только authenticated(), а не более строгие правила. Роли и authorities появятся позже, но уже сейчас важно чётко отделять «вошёл» от «имеет право».

Ошибка №3: использовать anyRequest().permitAll() как временный «фикс» и забыть вернуть назад.
Это классика разработки: «мне мешает security, выключу на минутку», а потом проходит три дня, вы делаете коммит, пушите — и внезапно у вас в репозитории «минутка» стала постоянной. Если уж вы временно открываете всё ради диагностики, делайте это очень осознанно и возвращайте обратно сразу после проверки.

Ошибка №4: не воспринимать конфигурацию как текст правил.
Если вы смотрите на authorizeHttpRequests как на «ну это такой DSL, я его не понимаю, просто скопирую», то дальше будет тяжело: появятся несколько зон, исключения, порядок правил, и вы начнёте ломать систему случайными движениями. Привычка проговаривать каждую строку («для этих запросов такое условие») кажется занудной, но она спасает от магии.

Ошибка №5: забывать, что authorization — это отдельная задача от authentication.
Иногда ожидают, что когда написали authenticated(), Spring Security «как-то сам поймёт, где логин». На самом деле authenticated() — это требование, а не механизм входа. Механизм аутентификации живёт в других частях Spring Security. Сегодня мы специально держим фокус на правилах доступа и учимся не смешивать эти два смысла в голове.

1
Задача
Spring Security, 5 уровень, 1 лекция
Недоступна
Полностью открытый baseline через `anyRequest().permitAll()`
Полностью открытый baseline через `anyRequest().permitAll()`
1
Задача
Spring Security, 5 уровень, 1 лекция
Недоступна
Полностью закрытый baseline через `anyRequest().authenticated()`
Полностью закрытый baseline через `anyRequest().authenticated()`
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ