Перейти к основному содержимому

Spring Boot — безопасность в продакшене

Разработчику Архитектору

Spring Boot — безопасность в продакшене

После практического старта Spring Security и JWT / Resource Server приложение защищено на уровне «учебного API». В production нужен отдельный слой: транспорт, заголовки, секреты, зависимости и процессы команды.

Ниже — десять практик, которые обычно входят в чеклист для Spring Boot (в духе industry cheat sheet от Snyk и Java Champions). Все примеры — Spring Security 6 и SecurityFilterChain, без устаревшего WebSecurityConfigurerAdapter.

Связанные материалы

ТемаГде углубиться
OAuth2 / OIDC, KeycloakОсновы ИБ — SSO
CSP, XSS, CSRF в браузереКак работают сайты — безопасность, CSP
Сканирование зависимостей в CIDevOps — SCA, конвейеры
Секреты вне GitЗабота о коде и данных
Профили и application-prodРекомендации по Java — профили

Сводный чеклист

ПрактикаЗачем
1HTTPS в prodШифрование трафика, защита cookie и токенов от перехвата
2Проверка зависимостей (SCA)Известные CVE в spring-*, Log4j, Jackson и транзитивных JAR
3CSRF для stateful webПодставные запросы из чужого сайта с cookie сессии
4Content Security PolicyСнижение риска XSS — белый список источников скриптов
5OpenID ConnectДелегированный вход (Google, Keycloak, корпоративный IdP)
6Хеширование паролейПароли в БД только через PasswordEncoder
7Актуальные версии Spring Boot / JDKЗакрытие уязвимостей в платформе
8Секреты в vault / envПароли БД и API-ключи вне репозитория
9Пентест / DASTOWASP ZAP, активное сканирование staging
10Security-ревью и gates в PRЧеловек + автоматика на каждом merge

1. HTTPS в production

Терминация TLS чаще всего на reverse proxy (nginx, Ingress, API Gateway). Spring Boot может дополнительно требовать HTTPS на уровне приложения:

@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.requiresChannel(channel -> channel.anyRequest().requiresSecure());
// authorizeHttpRequests, csrf, oauth2ResourceServer...
return http.build();
}
Профиль и локальная разработка

Правило requiresSecure() заставит браузер ходить по https://localhost. Для dev вынесите настройку в профиль или отключите канал в application-dev.yml. В prod TLS обязателен на границе сети; см. также заголовок Strict-Transport-Security в безопасности веб-приложений.


2. Тестировать зависимости на уязвимости

Spring Boot тянет десятки транзитивных библиотек. Software Composition Analysis (SCA) сравнивает версии с базой CVE.

ИнструментТипичное место
OWASP Dependency-CheckMaven/Gradle plugin, отчёт в CI
Snyk, Dependabot, RenovatePR с обновлением уязвимых версий
TrivyОбраз Docker + зависимости

Минимум для команды: скан mvn dependency:tree / Gradle lockfile на каждом PR и блокировка merge при Critical/High без исключения. Подробнее — SCA в DevOps.

<!-- пример: OWASP Dependency-Check в Maven -->
<plugin>
<groupId>org.owasp</groupId>
<artifactId>dependency-check-maven</artifactId>
<version>11.1.0</version>
<executions>
<execution>
<goals><goal>check</goal></goals>
</execution>
</executions>
</plugin>

3. CSRF — включать осознанно

Spring Security включает CSRF по умолчанию для приложений с сессией в cookie. В учебном REST CSRF отключали, потому что клиент шлёт только Authorization: Bearer без cookie.

Для SPA + cookie-сессия или классических форм нужна CSRF-защита. Если фронтенд читает токен из cookie, cookie не должна быть HttpOnly:

http.csrf(csrf -> csrf
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()));

Клиент (React, Vue) читает cookie XSRF-TOKEN и дублирует значение в заголовке X-XSRF-TOKEN при POST/PUT/DELETE. Теория — CSRF в веб-безопасности.


4. Content Security Policy (CSP)

Spring Security по умолчанию выставляет ряд security-заголовков, но CSP нужно задать явно:

http.headers(headers -> headers
.contentSecurityPolicy(csp -> csp.policyDirectives(
"default-src 'self'; " +
"script-src 'self' https://trustedscripts.example.com; " +
"object-src 'none'; " +
"report-uri /csp-report-endpoint/")));

Сначала политику можно включить в режиме Content-Security-Policy-Report-Only, собрать отчёты с /csp-report-endpoint/, затем ужесточить. Директивы и отчёты — Reporting API и CSP.


5. OpenID Connect (OIDC)

OIDC поверх OAuth2 добавляет ID Token (кто вошёл) и стандартный endpoint /userinfo для профиля.

В Spring Boot:

  • Клиент (браузерный вход «через Google») — spring-boot-starter-oauth2-client;
  • Resource Server (API принимает JWT) — spring-boot-starter-oauth2-resource-server, см. JWT в Spring Boot.
# application.yml — resource server против Keycloak / Auth0
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://idp.example.com/realms/my-realm

Роли из токена маппятся на GrantedAuthority; сценарий с Keycloak разобран в примере REST + Keycloak.


6. Хеширование паролей

Пароли никогда не хранят в открытом виде и не сравнивают строкой password.equals(stored).

Интерфейс Spring Security:

public interface PasswordEncoder {
String encode(CharSequence rawPassword);
boolean matches(CharSequence rawPassword, String encodedPassword);
}

Типичная настройка:

@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

// при регистрации
user.setPassword(passwordEncoder.encode(rawPassword));

// при логине Spring сравнивает через UserDetailsService + PasswordEncoder

Префикс {noop} из учебного примера допустим только в тестах. В БД — bcrypt, scrypt или делегирование в IdP (OIDC).


7. Актуальные релизы Spring Boot и JDK

Spring Initializr для новых проектов подтягивает свежие patch-версии BOM. Для существующих сервисов:

  • следите за Spring Boot release notes и CVE;
  • держите LTS JDK (17 или 21);
  • обновляйте patch (3.4.x3.4.y) по расписанию, major — по плану миграции.

Если обновление отложено, SCA и vendor-патчи закрывают часть уязвимостей в транзитивных JAR без смены major Spring.


8. Секреты вне кода и Git

Антипаттерн — пароль БД в application.properties в репозитории:

# так делать нельзя в prod
spring.datasource.password=SuperSecret123

Правильные варианты:

СпособКогда
Переменные окружения SPRING_DATASOURCE_PASSWORDKubernetes, VM, PaaS
HashiCorp Vault, Spring Cloud VaultЦентрализованное хранение, ротация
Секреты CI/CD (GitLab Variables, GitHub Secrets)Деплой без записи в образ
@Value("${db.password}")
String password; // значение приходит из vault/env при старте, не из Git

См. профили окружений и гигиену секретов.


9. Пентест и динамическое сканирование

OWASP ZAP (и аналоги) прогоняют staging: Spider находит ссылки, Active Scan — типовые уязвимости (XSS, SQLi, misconfiguration).

РежимНазначение
SpiderКарта URL приложения
Active ScanАвтоматические проверки по найденным точкам
API ScanOpenAPI/Swagger для REST

ZAP встраивают в CI как DAST после деплоя на test/stage — DevOps и OWASP ZAP. Результаты согласуют с ручным пентестом перед релизом.


10. Security-ревью и автоматика в pull request

Процесс дополняет технические настройки:

  • Ревью изменений в SecurityFilterChain, новых эндпоинтах, CORS, маппинге ролей — желательно с участием security-чемпиона команды;
  • SAST/SCA (SonarQube, Snyk, Dependency-Check) на каждом PR;
  • Чеклист для автора: новые публичные URL? секреты в диффе? логируются ли токены?

Связка с тестированием: security-сценарии в @WebMvcTest / @SpringBootTest с @WithMockUser, как в статье 272.


Пример — фрагмент prod-конфигурации

Объединение нескольких пунктов (упрощённо; политику URL подставьте под свой API):

@Configuration
@EnableWebSecurity
@Profile("prod")
public class ProductionSecurityConfig {

@Bean
SecurityFilterChain prodChain(HttpSecurity http) throws Exception {
http
.requiresChannel(ch -> ch.anyRequest().requiresSecure())
.csrf(csrf -> csrf
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()))
.headers(h -> h.contentSecurityPolicy(csp -> csp.policyDirectives(
"default-src 'self'; script-src 'self'; frame-ancestors 'none'")))
.authorizeHttpRequests(auth -> auth
.requestMatchers("/actuator/health", "/actuator/info").permitAll()
.requestMatchers("/actuator/**").hasRole("ADMIN")
.anyRequest().authenticated())
.oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()));
return http.build();
}

@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
Маршрут по разделу Java

Частые ошибки

ОшибкаПоследствиеЧто сделать
CSRF отключён «навсегда» после REST-туториалаCSRF на формах с cookieВключить CSRF для web UI; для pure Bearer API оставить off
Секреты в GitУтечка при fork/cloneEnv, Vault, ротация ключей
Только unit-тесты без ZAP/SCACVE и misconfig в prodSCA в CI + DAST на stage
{noop} или MD5 для паролейМгновенный компромисс БДBCryptPasswordEncoder или OIDC
CSP не настроенXSS через inline-скриптCSP + экранирование на фронте

Spring Security — старт · JWT · Spring Framework · Информационная безопасность

См. также

Другие статьи этого же раздела в боковом меню (как на странице "О разделе").