Spring Security — практический старт
Spring Security — практический старт
После первого Spring Boot API возникает вопрос: кто может вызывать эндпоинты? Публичный URL без защиты подходит для учебы; в реальном API нужны логин, роли, токены.
Spring Security — стандартная библиотека в Java-экосистеме: цепочка фильтров проверяет запрос до вашего @RestController.
Словарь для старта
| Термин | Простое объяснение |
|---|---|
| Аутентификация | «Кто вы?» — проверка логина и пароля (или токена) |
| Авторизация | «Что вам можно?» — доступ к /api/private, роль ADMIN |
| Фильтр (Filter) | Код, который обрабатывает HTTP до контроллера; Security — набор таких фильтров |
| SecurityFilterChain | Упорядоченный список правил: какой URL открыт, какой требует входа |
| HTTP Basic | Логин и пароль в заголовке Authorization: Basic ... (Base64); удобно для curl |
| 401 Unauthorized | «Вы не представились» — нет или неверные учётные данные |
| 403 Forbidden | «Вы вошли, но права не те» — роль или scope не подходят |
| CSRF | Атака через подставной запрос из браузера; для stateless REST часто отключают осознанно |
| UserDetailsService | Источник пользователей для Spring (здесь — список в памяти) |
В этой статье — минимальный, но рабочий сценарий:
GET /api/public— без авторизации;GET /api/private— только с логином/паролем (HTTP Basic);- конфигурация через
SecurityFilterChain(Spring Security 6+, Boot 3).
Обзор теории: Spring Framework · тесты API: JUnit 5.
Spring Security - цепочка фильтров вместо проверок в контроллере
Проверка if (apiKey == null) в каждом @GetMapping дублируется, легко забыть новый эндпоинт, сложно менять правила централизованно.
Spring Security держит правила в одном SecurityConfig: «эти пути открыты, остальное — только после входа». Контроллер занимается бизнес-логикой.
Как выглядит запрос с Basic-авторизацией
Клиент кодирует логин:пароль в Base64 и шлёт заголовок:
GET /api/private HTTP/1.1
Host: localhost:8080
Authorization: Basic ZGVtbzpzZWNyZXQ=
curl -u demo:secret делает это автоматически. Сервер сравнивает с пользователем из UserDetailsService; при успехе запрос доходит до контроллера, при ошибке — 401 без вызова вашего метода.
Что получится
curl -s http://localhost:8080/api/public
# {"message":"ok"}
curl -s http://localhost:8080/api/private
# 401 Unauthorized
curl -s -u demo:secret http://localhost:8080/api/private
# {"message":"hello, demo"}
Зависимости
На start.spring.io добавьте Spring Web и Spring Security к существующему проекту из 271 или создайте новый.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
Конфигурация безопасности
Класс SecurityConfig — единственное место, где задаются правила (не размазывайте по контроллерам).
package com.example.demo.security;
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.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable()) // для учебного REST; в проде — осознанно
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/public", "/actuator/health").permitAll()
.anyRequest().authenticated())
.httpBasic(Customizer.withDefaults());
return http.build();
}
@Bean
UserDetailsService userDetailsService() {
return new InMemoryUserDetailsManager(
User.withUsername("demo")
.password("{noop}secret")
.roles("USER")
.build());
}
}
Разбор конфигурации по строкам:
| Фрагмент | Что делает |
|---|---|
@Configuration + @EnableWebSecurity | Класс с правилами безопасности включён в контекст Spring |
csrf.disable() | Отключает проверку CSRF-токена; для учебного REST без cookie-сессий это типично; для форм в браузере — включайте обратно |
requestMatchers("/api/public", ...).permitAll() | Эти URL доступны без логина |
anyRequest().authenticated() | Все остальные пути — только после успешной аутентификации |
httpBasic(...) | Включить схему Basic (заголовок Authorization) |
InMemoryUserDetailsManager | Пользователи хранятся в RAM; после перезапуска — те же логины |
User.withUsername("demo").password("{noop}secret") | {noop} = пароль без хеширования (только обучение) |
.roles("USER") | Роль с префиксом ROLE_USER внутри; в правилах пишут hasRole("USER") |
BCrypt в проде: пароль хранят как хеш ($2a$10$...), в конфиге указывают PasswordEncoder bean. Spring сравнивает введённый пароль с хешем, открытый текст в БД не лежит.
:::caution Продакшен
In-memory пользователи и {noop} — только для обучения. В реальных системах: БД/LDAP, BCrypt (passwordEncoder), JWT или OAuth2 Resource Server — см. 27.md.
:::
Контроллер
package com.example.demo.web;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
@RestController
public class ApiController {
@GetMapping("/api/public")
public Map<String, String> publicEndpoint() {
return Map.of("message", "ok");
}
@GetMapping("/api/private")
public Map<String, String> privateEndpoint(@AuthenticationPrincipal UserDetails user) {
return Map.of("message", "hello, " + user.getUsername());
}
}
@AuthenticationPrincipal UserDetails user — после успешного входа Spring кладёт объект пользователя в контекст безопасности; параметр метода заполняется автоматически. Так вы получаете user.getUsername() без разбора заголовка вручную.
Разбор эндпоинтов:
| Метод | URL | Кто вызывает | Результат |
|---|---|---|---|
publicEndpoint | /api/public | Любой | {"message":"ok"} |
privateEndpoint | /api/private | Только с Basic demo:secret | Приветствие с именем пользователя |
Цепочка запроса (как это «ощущается»)
HTTP → SecurityFilterChain → (аутентификация?) → DispatcherServlet → @RestController
Если фильтры вернули 401, ваш метод контроллера не вызовется — это и есть «защита по умолчанию».
Тест с MockMvc (опционально)
В spring-boot-starter-test уже есть Spring Security Test:
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@SpringBootTest
@AutoConfigureMockMvc
class ApiControllerTest {
@Autowired MockMvc mvc;
@Test
void publicOk() throws Exception {
mvc.perform(get("/api/public")).andExpect(status().isOk());
}
@Test
void privateUnauthorized() throws Exception {
mvc.perform(get("/api/private")).andExpect(status().isUnauthorized());
}
@Test
@WithMockUser(username = "demo")
void privateWithUser() throws Exception {
mvc.perform(get("/api/private")).andExpect(status().isOk());
}
}
Разбор теста MockMvc:
| Аннотация | Зачем |
|---|---|
@SpringBootTest | Поднимает полный контекст приложения |
@AutoConfigureMockMvc | Внедряет MockMvc — HTTP без реального сетевого порта |
mvc.perform(get(...)) | Имитация GET-запроса |
@WithMockUser | Подставляет «вошедшего» пользователя без реального Basic |
Так вы проверяете правила Security без ручного Base64 в каждом тесте.
Подробнее о тестах — 291.md.
Частые ошибки
| Симптом | Причина |
|---|---|
| Все эндпоинты 401 | Нет permitAll для нужных путей |
| 401 с правильным паролем | Неверный префикс {noop} или другой encoder |
| CSRF 403 на POST | Для API часто отключают CSRF или используют токен |
| Whitelabel 404 | Путь контроллера не совпадает с requestMatchers |
Что попробовать
- Добавить роль
ADMINи правило.requestMatchers("/api/admin/**").hasRole("ADMIN"). - Вынести логин/пароль в
application.yml(spring.security.user.name). - Следующий уровень — JWT и Resource Server.
Дальше
JWT · Ошибки REST · Testcontainers · JPA · Spring Boot
См. также
Другие статьи этого же раздела в боковом меню (как на странице «О разделе»). Основы Java - устройство JDK/JVM, модель компиляции и базовые принципы платформонезависимого выполнения. Java — это объектно-ориентированный язык программирования общего назначения, который работает на принципах «напиши один раз, запускай в любом месте». Набор советов, правил, принципов и обычаев в разработке на этом языке. История Java — от проекта Green и Oak до OpenJDK, LTS-релизов и современной платформы (модули, records, виртуальные потоки). Библиотеки, фреймворки, инструменты сборки, тестирования, развёртывания и мониторинга. Что такое пакет и пакетная структура, как собираются проекты на Java. Справочник-шпаргалка по конфигурациям в Java — типы, синтаксис, стандартная библиотека, типовые паттерны. Не заменяет пошаговое обучение. Учебный курс — раздел. Гайд по установке и настройке с написанием первой программы и её запуском. Практические примеры — консольные утилиты, композиция классов в мини-игре и первое Swing-приложение. Точки останова, пошаговое выполнение, панели Variables и Call Stack — практика отладки в IntelliJ IDEA. Кавычки, точки, запятые, скобки и прочие знаки препинания. Это полный справочник всех ключевых слов языка Java, включая основные, контекстные и зарезервированные слова.Основы языка Java
Что требуется знать перед началом изучения языка программирования Java
Рекомендации по разработке на Java
История языка Java
Экосистема Java-приложений
Структура и сборки Java-проектов
Справочник по конфигурациям в Java
Первая программа на Java
Простые приложения на Java
Отладка Java-кода в IDE
Синтаксис и пунктуация в Java
Ключевые слова в Java