1. Основи HTTP Basic
Коли ви все робите через браузер, formLogin виглядає майже ідеально: відкрили сторінку, ввели логін і пароль — і система далі «памʼятає», хто ви. Але щойно в гру вступають API-клієнти (Postman, curl, інтеграції, тестові скрипти), починається легкий смуток: їм не хочеться «ходити на сторінку входу», ловити перенаправлення й удавати браузер. У цей момент нам потрібен механізм, який чесно працює на рівні одного HTTP-запиту.
Уявіть, що ви перевіряєте наш ендпоінт /api/me через API-клієнт. Він не «тримає вкладку браузера» і не живе у світі «сторінок», а робить один конкретний запит: «дай дані». І далі головне питання — як цей запит доведе, що його робить конкретний користувач? У моделі на основі сесії браузер приносить cookie з ідентифікатором сесії. В API-клієнті це теж можливо, але для навчання й для прозорого розуміння часто зручніше почати з прямішої схеми.
І ось тут зʼявляється HTTP Basic — простий, майже «залізобетонний» спосіб автентифікації: клієнт додає облікові дані безпосередньо до запиту. Не «колись увійшов», а «ось мій логін і пароль для цього запиту». Це дуже добра опора, щоб мозок перестав плутати два питання: хто робить запит (authentication) і що йому можна (authorization).
Ментальна модель: «паспорт на вході — щоразу»
HTTP Basic зручно розуміти без магії та без «Spring-заклинань». Модель буквально така: кожен запит до захищеного ресурсу несе облікові дані, і сервер намагається автентифікувати користувача просто під час обробки цього запиту. Це схоже не на «отримав браслетик на вході до фестивалю» (сесія), а на «охоронець на вході щоразу просить паспорт» (Basic). Так, це менш зручно, зате дуже прозоро.
Технічно облікові дані надходять у HTTP-заголовку Authorization. Клієнт каже: «Я хочу використовувати схему Basic». Далі він передає дані так, щоб їх можна було витягнути. Поки що важливо не те, як саме влаштований рядок у заголовку, а сам зміст: у кожному захищеному запиті є дані, за якими сервер може перевірити логін і пароль.
З погляду того, де зберігається стан, Basic-підхід майже завжди описують як stateless: серверу не потрібно памʼятати «вчорашній логін» у сесії, бо клієнт приносить облікові дані щоразу. Однак не переплутайте: stateless тут означає лише одне — особу не відновлюють за вже збереженою сесією. Це ще не окремий ендпоінт для входу, не життєвий цикл токена і взагалі не історія про bearer-токени. Нам поки що потрібен лише сам принцип: облікові дані на кожен запит.
Сервер усе одно зберігає користувачів — бодай in-memory на нашому етапі — і їхні хеші паролів, інакше він не зможе перевірити пароль.
Щоб закріпити картину, давайте намалюємо спрощений шлях запиту в Basic-моделі:
sequenceDiagram
participant C as "API-клієнт Postman/curl"
participant S as "Spring Security всередині застосунку"
participant A as "Наш контролер /api/me"
C->>S: "GET /api/me + облікові дані в Authorization"
S->>S: "Перевірка логіна/пароля (через UserDetailsService + PasswordEncoder)"
alt успіх
S->>A: "Пропускає запит далі (є поточний користувач)"
A->>C: "200 OK + дані"
else невдача / немає облікових даних
S->>C: "401 Unauthorized (потрібно автентифікуватися)"
end
Схема спеціально «людська». Ми поки що не обговорюємо конкретні фільтри та класи, бо наша мета тут — відчути механіку на рівні «клієнт приносить облікові дані в запиті».
2. HTTP Basic і правила доступу
Дуже типова помилка новачка виглядає так: «О, я увімкнув httpBasic() — отже, тепер безпека налаштована». Це як купити замок на двері й вирішити, що тепер у вас налаштовано права доступу до кімнат, сейфів і холодильника. Замок відповідає на питання «хто може увійти в дім», а правила всередині дому — на питання «куди йому можна йти».
HTTP Basic відповідає тільки за автентифікацію: він допомагає серверу зрозуміти, хто робить запит. А от питання «що цьому користувачеві можна» лишається там само, де й було: у наших authorizeHttpRequests(...), requestMatchers(...), authenticated(), hasRole(...) і, в майбутньому, hasAuthority(...).
І це, до речі, чудова новина: наша матриця доступу в проєкті не змінюється. У нас і далі:
- /api/public/** можна всім,
- /api/me/** — будь-якому автентифікованому користувачеві,
- /api/editor/** — тільки EDITOR,
- /api/admin/** — тільки ADMIN.
Змінюється тільки спосіб доставки особи в запит: замість «я один раз увійшов через форму і тепер мене впізнають за cookie» ми кажемо «ось мій логін і пароль у кожному запиті». Якщо ви триматимете в голові це розділення шарів, Spring Security перестане здаватися магією і почне виглядати як набір логічних перемикачів.
Невелика таблиця, щоб краще відчути різницю, але без заглиблення в порівняння:
| Питання | Сесія + вхід через форму | HTTP Basic |
|---|---|---|
| Як сервер упізнає користувача? | За cookie з ідентифікатором сесії після входу | За обліковими даними, доданими до запиту |
| Чи потрібно спочатку входити окремим кроком? | Так, є окремий процес входу | По суті ні: автентифікація відбувається разом із запитом до ресурсу |
| Чи змінюються правила hasRole(...) / authenticated()? | Ні | Ні |
Головна думка: HTTP Basic — це не «права доступу», а «спосіб представитися».
3. Підключення HTTP Basic у проєкті
Поки достатньо побачити одну річ: Basic вмикається явно через httpBasic(...) поруч зі звичайними правилами доступу. Цієї межі вже вистачає, щоб відділити механізм автентифікації від правил доступу; внутрішня ланка нам тут ще не потрібна.
Нижче — мінімальна конфігурація, яка залишає публічну зону відкритою, а все інше вимагає автентифікації, і додає HTTP Basic як механізм, яким клієнт може виконати цю автентифікацію.
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
class SecurityConfig {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
// Правила доступу: хто куди може ходити
.authorizeHttpRequests(a -> a
.requestMatchers("/api/public/**").permitAll() // публічна зона без входу
.anyRequest().authenticated() // усе інше вимагає автентифікації
)
// Механізм автентифікації: як клієнт «представляється» в запиті
.httpBasic(Customizer.withDefaults())
.build();
}
}
Якщо додати в конфігурацію /api/editor/** і /api/admin/**, змінюються тільки requestMatchers(...) і hasRole(...); сам принцип не змінюється. authorizeHttpRequests(...) і далі малює карту доступу, а .httpBasic(...) лише каже, як API-клієнт приносить облікові дані.
І ще одна важлива річ, яку краще зафіксувати відразу. HTTP Basic передає облікові дані так, що логін і пароль можна відновити із запиту. Отже, такі запити мають іти захищеним транспортом. На локальній машині ми іноді розслабляємося, але як інженерну звичку тримаємо в голові: Basic — тільки через HTTPS. Це не параноя, це просто математика життя.
4. Поточний користувач у контролері
Після успішної автентифікації контролер не має працювати із заголовком Authorization напряму. Для нього вже важливий лише результат: Spring Security встановив поточного користувача, і бізнес-код отримує звичайний Principal.
@GetMapping("/api/me")
String me(Principal principal) {
return "Ви: " + principal.getName();
}
У цьому місці важливо побачити сам ефект: контролер не декодує Base64, не звіряє пароль рядок за рядком і взагалі не торкається облікових даних руками. Він працює з уже автентифікованим користувачем, тож йому однаково, чи це був formLogin, чи HTTP Basic. За перевіркою все одно стоїть звичайний ланцюжок перевірки логіна й пароля з UserDetailsService і PasswordEncoder, а не саморобний розбір заголовків.
5. Корисні сценарії для HTTP Basic
HTTP Basic особливо хороший там, де клієнт і так живе заголовками: Postman, curl, тестові скрипти, внутрішні інтеграції, невеликі сервісні утиліти. У таких сценаріях не потрібен браузерний процес входу; потрібен передбачуваний запит, передбачуваний 401 і зрозумілий звʼязок між обліковими даними та відповіддю.
Але та сама прямолінійність швидко стає мінусом там, де потрібен нормальний користувацький досвід або краще керований стан між запитами. Тому поки сприймайте Basic як чесну базу для API і зручну навчальну опору, а не як універсальну відповідь на всі сценарії автентифікації.
6. Типові помилки під час роботи з HTTP Basic
Помилка № 1: плутати HTTP Basic з авторизацією за ролями.
Іноді здається, що “Basic” — це вже щось про права. Ні: Basic — це спосіб показати «хто я». Ролі та правила hasRole(...) — це про «що мені можна». Якщо ви тримаєте ці рівні окремо, конфігурація лишається читабельною, а поведінка — передбачуваною.
Помилка № 2: думати, що вмикання .httpBasic(...) скасовує потребу в authorizeHttpRequests(...).
Буває спокуса: «Ну раз я вмикнув Basic, значить усе захищено». Насправді ви просто додали механізм автентифікації. А якщо при цьому правила доступу занадто широкі (наприклад, десь випадково permitAll()), Basic ніяк не врятує: ендпоінт залишиться відкритим.
Помилка № 3: переносити браузерне мислення в API-мислення.
Після formLogin мозок звикає до моделі «увійшов один раз — і забув». HTTP Basic працює інакше: клієнт має додавати облікові дані до захищених запитів. Якщо чекати перенаправлень, HTML-сторінок або «статусу входу» як у браузері, вийде плутанина, хоча Spring Security робить усе правильно.
Помилка № 4: зберігати «тимчасові» raw-паролі в конфігурації користувачів.
Іноді з лінощів хочеться написати .password("secret") і піти далі. Але ми вже домовилися: пароль зберігається і порівнюється через PasswordEncoder, навіть якщо користувач in-memory. Інакше ви самі привчаєте себе до неправильного підходу, а security-курс перетворюється на курс «як красиво наступати на граблі».
Помилка № 5: ставитися до Basic як до «приховування пароля», а не до його передавання.
HTTP Basic — це саме передавання облікових даних у запиті. Тому, якщо ви випадково залогуєте заголовки або «поділитеся» готовим запитом із колегою, ви фактично поділитеся логіном і паролем. З Basic дуже швидко починаєш розуміти цінність дисципліни: менше логів, менше копіювання й вставляння, більше обережності.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ