Как стать автором
Обновить
139.76

Что нового в Spring Boot 3.4: Spring Security

Уровень сложностиПростой
Время на прочтение10 мин
Количество просмотров2.8K
Автор оригинала: Josh Long

Команда Spring АйО перевела статью об улучшениях Spring Security 6.4.1 в новом релизе.

В новой версии поддержаны современные методы аутентификации, такие как Passkeys и одноразовые OTTs токены. Josh Long называет этот релиз doozie - что-то сильно потрясающее. Действительно ли это так?



Spring Security 6.4.1 — это универсальное решение для аутентификации и авторизации, а этот релиз просто потрясающий! Release notes включает в себя большое количество фич!

Release notes — это ложь!

То есть, они не ложь. Они просто плохо передают и описывают, насколько этот релиз великолепен. В этом релизе больше функций, ориентированных на пользователя, чем во многих предыдущих. Возможно, это мой любимый релиз Spring Security, как минимум с тех пор, как появился DSL для конфигурации на Java!

Посмотрите на этот release notes. Видите эти крошечные разделы о Passkeys и One-Time Token Login? Да. Это и есть ложь. Эти вещи заслуживают целых глав! Мы ещё вернёмся к ним. Обещаю. А пока давайте быстро взглянем на остальные нововведения. Их так много.

  • Огромные улучшения в методах безопасности и модели компонентов Spring Security, включая улучшенную поддержку механизма мета-аннотаций Spring Framework и шаблонов аннотаций.

  • Приложения на основе AOT и GraalVM Native Image теперь корректно работают с функцией @AuthorizeReturnObject и могут ссылаться на бины в обратных вызовах @PreAuthorize и @PostAuthorize.

  • Удобный API SecurityAnnotationScanners предоставляет возможность сканировать security аннотации для добавления функций выбора и шаблонов Spring Security в custom аннотации.

  • Поддержка OAuth 2.0 стала ещё лучше! Метод oauth2Login() теперь принимает OAurth2AuthorizationRequestResolver как @Bean.

  • ClientRegistrations теперь поддерживает конфигурацию, полученную из внешних источников.

  • Реактивный DSL Spring Security теперь поддерживает loginPage().

  • Поддержка back-channel OIDC теперь принимает токены выхода типа logout+jwt.

  • Spring RestClient теперь можно настроить с OAuth2ClientHttpRequestInterceptor для выполнения защищённых запросов к ресурсам. Он может передавать ваш токен в нижележащую службу при выполнении HTTP-запроса.

  • Обмен токенов теперь поддерживает refresh токены.

  • Поддержка SAML 2.0 также значительно улучшена! Поддержка OpenSAML 5 теперь доступна. Регистрация EntityIDs упрощена (подробнее можно почитать тут и тут).

  • Asserting Parties теперь могут обновляться в фоновом режиме в зависимости от истечения срока действия метаданных.

  • Теперь вы можете подписывать relying party метаданные.

  • Чтобы соответствовать стандарту SAML 2.0, конечная точка метаданных теперь использует MIME-type application/samlmetadata+xml.

  • В обычных приложениях Spring MIME-typeWeb теперь поддерживаются токены CSRF BREACH.

  • Более настраиваемые куки Remember Me.

  • Фильтры цепочки Spring Security теперь лучше отмечают некорректные конфигурации.

  • ServerHttpSecurity теперь подхватывает объекты ServerWebExchangeFirewall как бины.

  • Этот релиз также приносит улучшенную и более грубую интеграцию для прозрачности процесса авторизации, аутентификации и просматриваемости запросов.

  • AclAuthorizationStrategyImpl поддерживает тип RoleHierarchy, который тоже довольно новый!

  • Поддержка Kotlin значительно улучшена!

  • Технически Spring Authorization Server не является частью Spring Security, но я упомяну его здесь для удобства. Он включает множество новых функций, включая гораздо более согласованную и лаконичную конфигурацию DSL. Теперь можно развернуть полный OAuth IDP с одной строкой кода на Java в типичном приложении Spring Security и несколькими строками конфигурации в вашем properties или YAML-файле. Это ТАК удобно.

Теперь вернёмся к обсуждению Passkeys и одноразовых токенов.

Улучшенные пароли

Давайте рассмотрим простое приложение.

У нас будет HTTP-контроллер, который мы хотим защитить:

package com.example.bootiful_34.security;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.security.Principal;
import java.util.Map;

@Controller
@ResponseBody
class SecuredController {

	@GetMapping("/admin")
	Map<String, String> admin(Principal principal) {
		return Map.of("admin", principal.getName());
	}

	@GetMapping("/")
	Map<String, String> hello(Principal principal) {
		return Map.of("user", principal.getName());
	}
}

Чтобы защитить его, нам нужно будет определить SecurityFilterChain с некоторыми стандартными компонентами: HTTP form login, некоторые правила авторизации и т. д.

@Bean
SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
   return httpSecurity
  		     .authorizeHttpRequests(requests -> requests
             .requestMatchers("/admin").hasRole("ADMIN")
             .requestMatchers("/error").permitAll()
             .anyRequest().authenticated()
             )
          .formLogin(Customizer.withDefaults())
          // ...
          .build();
}

Нам нужно будет настроить Spring Security для работы с пользователями в нашей системе, поэтому давайте предоставим UserDetailsService:

package com.example.bootiful_34.security;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportRuntimeHints;
import org.springframework.http.MediaType;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
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
@ImportRuntimeHints(UiResourcesRuntimeHintsRegistrar.class)
class SecurityConfiguration {

	@Bean
	UserDetailsService userDetailsService() {
		var josh = User.withUsername("josh").password("pw").roles("USER").build();
		var rob = User.withUsername("rob").password("pw").roles("USER", "ADMIN").build();
		return new InMemoryUserDetailsManager(josh, rob);
	}
	
	// ...
	
}

Это приложение имеет двух пользователей: josh и rob. У josh есть одна роль — USER, а у rob — роли ADMIN и USER.

В реальном приложении вместо регистрации пользователей вручную вы бы использовали альтернативную реализацию UserDetailsService, указывающую на источник данных для идентификации. Или, как минимум, SQL-базу данных, где пароли хранятся в зашифрованном виде и никогда не сохраняются в открытом тексте. В данном случае я регистрирую пользователей и их пароли напрямую, но в хорошо спроектированной системе использование паролей должно быть минимизировано насколько это возможно. Ваше привычное представление о паролях, скорее всего, ошибочно.

Национальный институт стандартов и технологий (NIST), организация при Министерстве торговли США, выпустил обновлённые рекомендации по созданию паролей в прошлом году (2024).

Специальная публикация NIST 800-63B содержит конкретные рекомендации по созданию, обработке, обновлению и хранению паролей (называемых "запоминаемыми секретами",  “memorized secrets”), а также описывает рекомендации по использованию альтернатив, таких как одноразовые токены и аппаратные аутентификаторы (например, YubiKeys/WebAuthn). Давайте рассмотрим некоторые из их рекомендаций.

Создание достаточной безопасности паролей

Если ваша система будет использовать пароли, нужно учитывать множество факторов. К счастью, Spring Security может выполнить большую часть (или даже все) из приведённых рекомендаций за вас.

Пароли, выбираемые пользователями, должны содержать не менее восьми символов, а пароли, случайно сгенерированные CSP (поставщиком услуг идентификации), должны содержать не менее шести символов. Правила сложности (например, требование использования символов разного регистра, чисел и специальных символов) не рекомендуются. Пароли не должны иметь произвольных ограничений (например, ограничение максимальной длины или исключение определённых символов). Пароли должны проверяться на наличие в списках часто используемых, скомпрометированных или ожидаемых паролей (например, словарные слова, повторяющиеся шаблоны, термины, специфичные для сервиса). Если пароль помечен как слабый, пользователь должен выбрать более надёжный вариант. Рекомендуется использование индикаторов сложности пароля или обратной связи, чтобы помочь пользователям создавать более надёжные пароли.

Рекомендации предполагают возможность использования функции копирования и вставки для поддержки использования менеджеров паролей, а также предоставление опции отображения введённого пароля для минимизации ошибок ввода. Документ предлагает ограничить количество подряд неудачных попыток входа до 100 и использовать CAPTCHA, увеличивать задержки времени или применять другие адаптивные меры для предотвращения блокировки аккаунтов из-за злоупотреблений.

Интересно, что политика повторной аутентификации должна зависеть от уровня уверенности (assurance level) (например, повторная аутентификация требуется каждые 12 часов или через 30 минут бездействия для высокого уровня уверенности). В то же время смена пароля не должна требоваться произвольно или периодически. Смена должна быть обязательной только при наличии доказательств компрометации. Рекомендуется, чтобы пароли хешировались и солились с использованием односторонних функций генерации ключей (например, PBKDF2, BCrypt или Argon2). Spring Security по умолчанию использует BCrypt.

Лучший пароль — это отсутствие пароля

NIST предлагает использовать альтернативы паролям, такие как одноразовые токены (OTP). Однофакторные устройства OTP генерируют одноразовые пароли, основанные на времени или счётчике. Такие OTP должны быть криптографически защищёнными и иметь как минимум 20 бит энтропии. Многофакторные устройства OTP требуют второго фактора аутентификации (например, PIN-кода или биометрии) перед генерацией OTP.

Аутентификация через сторонние каналы использует вторичные каналы связи (например, мобильные устройства) для доставки OTP или подтверждения, что требует защищённых каналов и исключает использование слабых методов. Также активно рекомендуются аппаратные аутентификаторы (например, YubiKeys, WebAuthn). WebAuthn является частью альянса FIDO и считается надёжным методом аутентификации, устойчивым к фишингу. Аппаратные аутентификаторы, такие как YubiKeys, используют криптографические протоколы для обеспечения безопасности.

Многофакторные криптографические устройства должны сочетать «то, что у вас есть» (например, YubiKey) с «тем, что вы знаете» (например, PIN-код) или «тем, кто вы есть» (например, биометрия). Ключи должны оставаться внутри защищённого от взлома оборудования и быть недоступными для извлечения.

Отдавая приоритет этим методам, NIST подчёркивает важность перехода к многофакторной аутентификации (MFA) и криптографическим решениям как более безопасным альтернативам традиционным паролям.

Переход на одноразовые токены (OTTs)

В контексте приложения Spring Security одноразовый токен аутентифицирует пользователя, полагаясь на сторонний фактор, доступный только ему, например, возможность получать текстовые сообщения или доступ к электронной почте. Вы, вероятно, уже сталкивались с этой функцией в других местах. Вы заходите на сайт, вводите своё имя пользователя, и сайт отправляет вам письмо со ссылкой, по которой можно войти. Такие ссылки иногда называют "магическими ссылками".

Spring Security не предоставляет интеграции, например, с вашим почтовым провайдером или приложением для обмена сообщениями. Для этого вы можете использовать Sendgrid, Twilio или любой из множества других сервисов. Однако Spring Security предоставляет механизмы для создания и аутентификации с помощью ссылки.

Вот небольшая часть кода, которую нужно добавить в конфигурацию Spring Security, чтобы настроить это:

@Bean
SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
        return httpSecurity
				// ..
				.oneTimeTokenLogin(configurer -> configurer.tokenGenerationSuccessHandler(
                    (request, response, oneTimeToken) -> {
                         var msg = "go to http://localhost:8080/login/ott?token=" + oneTimeToken.getTokenValue();
                         System.out.println(msg);
                         response.setContentType(MediaType.TEXT_PLAIN_VALUE);
                         response.getWriter().print("you've got console mail!");
                     }))
                // ..                        
                .build();
}

Видите? Это всего лишь одна лямбда, в которой вы передаёте достаточно контекста, чтобы создать и отправить ссылку пользователю. После её нажатия пользователь сможет войти в систему. Легко! В этом примере я просто логинюсь, нажимая на ссылку в консоли. Конечно, вы можете отправить ссылку по электронной почте или использовать более реалистичный способ.

Это невероятно удобно и избавляет пользователей от необходимости помнить или, что ещё хуже, делиться двумя паролями. Надеюсь, они уже защитили свои пароли от почты! Я бы предпочёл, чтобы у них был один хороший пароль, который они не используют на разных сайтах, чем десяток слабых и общих паролей.

Ещё один подход — дополнительный уровень безопасности — может включать что-то более сложное, чем текстовая ссылка, которую потенциальный хакер мог бы перехватить. Например, ваш отпечаток пальца, скан лица или отдельный аппаратный ключ. Вопрос в том: как интегрировать такие технологии туда, где они нужны? Для их работы необходимо, чтобы браузеры, серверная логика, операционные системы и другие компоненты поддерживали единый стандартный протокол.

И вы будете рады узнать, что практически все этим уже занимаются. Протокол, о котором идёт речь, называется WebAuthn, а группа, стоящая за ним, — FIDO Alliance. У альянса FIDO очень широко распространенная поддержка! Вот некоторые из его самых известных членов: DELL, Apple, Google, Intuit, NTT DOCOMO, Microsoft, Meta, LastPass, DashLane, Bank Of America, 1Password, Intel, CISCO, CVS Health, American Express, VISA и другие. Суть в том, что за этот подход выступают организации, чьи деньги действительно на кону. И браузеры тоже поддерживают! Все основные браузеры — Chrome, Safari, Edge, Firefox, Safari на iOS, Chrome на Android, Chrome на iOS и Edge на iOS — поддерживают этот протокол. Он повсюду! А теперь, благодаря этому релизу Spring Security, он может легко появиться и в вашем приложении.

Вот соответствующая конфигурация в примере нашей цепочки фильтров Spring Security:

@Bean
SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
     return httpSecurity
            // ... 
             .webAuthn(c -> c
                    .rpId("localhost")
                    .rpName("bootiful passkeys")
                    .allowedOrigins("http://localhost:8080")
             )
             // ...
            .build();
}

Перезапустите ваше приложение, затем войдите по адресу localhost:8080/webauthn/register. Зарегистрируйте ваш Passkey. Я использую экосистему Apple, поэтому мне предлагают пройти FaceID на моём iPhone или использовать TouchID на моём MacBook с чипом Apple Silicon. После этого браузер сохраняет Passkey в системной цепочке ключей операционной системы. Теперь он синхронизирован через iCloud. Таким образом, у меня есть не только надёжный способ входа, но он также привязан к моей учётной записи iCloud. Это значит, что я могу войти с помощью FaceID на одном устройстве и TouchID на другом без дополнительной настройки!

Чтобы увидеть это в действии, выйдите из системы: localhost:8080/logout.

Безопасность, которая не только очень хороша, но и удобна для пользователей! Здорово, не так ли?

Присоединяйтесь к русскоязычному сообществу разработчиков на Spring Boot в телеграм — Spring АйО, чтобы быть в курсе последних новостей из мира разработки на Spring Boot и всего, что с ним связано.

Теги:
Хабы:
Всего голосов 7: ↑7 и ↓0+9
Комментарии0

Публикации

Информация

Сайт
t.me
Дата регистрации
Численность
11–30 человек