Погружение в Spring Security для Servlet API
Александр КосаревSpring Security - это очень мощный и гибкий фреймворк, применяемый для обеспечения безопасности приложений на платформе Java, преимущественно веб-приложений, основанных на Spring Framework. Впрочем, он может применяться и в проектах на основе Jakarta Servlet API, не использующих Spring Framework.
Spring Security предоставляет средства для реализации аутентификации, авторизации и защиты от эксплойтов. Основными компонентами Spring Security являются фильтры - экземпляры классов, реализующих интерфейс Filter. В приложении на основе Servlet API, прежде чем запрос будет обработан сервлетом, он должен пройти через цепочку фильтров, каждый из которых может модифицировать запрос и ответ, а так же при необходимости приостанавливать обработку запроса.
class MyCustomFilter implements Filter {
void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// Выполнить действия до продолжения фильтрации
chain.doFilter(request, response); // Продолжить фильтрацию
// Выполнить действия после фильтрации
}
}
Важно! В нормальных условиях фильтр всегда должен продолжать процесс фильтрации при помощи chain.doFilter(request, response), в противном случае запрос никогда не достигнет своей цели - сервлета, который способен его обработать. Если же запрос не удовлетворяет каким-либо требованиям фильтра, то вызывать chain.doFilter(request, response) не надо, вместо этого нужно отправить клиенту ответ со статусом и содержимым, корректно описывающими причину отказа в обработке запроса.
class MyCustomFilter implements Filter {
void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// Проверяем наличие у пользователя прав на
// открытие запрашиваемой страницы
boolean hasAccess = chechAccess(request);
if (hasAccess) {
chain.doFilter(request, response); // Продолжить фильтрацию
} else {
response.sendError(403, "У тебя нет доступа...");
}
// Выполнить действия после фильтрации
}
}
Spring Security использует фильтры, зарегистрированные в контексте приложения Spring, и которые недоступны сервлет-контейнеру. Чтобы сервлет-контейнер мог использовать фильтры Spring Security, в приложении необходимо зарегистрировать компонент класса DelegatingFilterProxy, который является связующим звеном между сервлет-контейнером и контекстом приложения Spring. Если вы используете Spring Boot, то этого делать не надо - разработчики Spring Boot уже заранее сконфигурировали интеграцию Spring Security за вас.
DelegatingFilterProxy делегирует обработку запроса фильтру, зарегистрированному в контексте приложения, и в случае со Spring Security это будет FilterChainProxy - фильтр, использующий для обработки запроса цепочки фильтров безопасности - SecurityFilterChain. При обработке запроса FilterChainProxy применит все фильтры цепочки фильтров к запросу. Если в контексте приложения сконфигурировано несколько цепочек фильтров безопасности, то FilterChainProxy выберет наиболее подходящую в зависимости от параметров запроса.
Если запрос в процессе фильтрации успешно проходит все фильтры, то он достигает своей цели - сервлета, который сможет его обработать, в случае со Spring Framework это будет DispatcherServlet.
Настройка цепочек фильтров безопасности
Для включения поддержки Spring Security в приложении один из конфигурационных классов нужно отметить аннотацией @EnableWebSecurity:
@Configuration
@EnableWebSecurity
class SecurityBeans {
}
Впрочем, если вы используете Spring Boot, то делать вам это опять же не требуется.
Для настройки цепочки фильтров безопасности необходимо зарегистрировать в контексте приложения компонент типа SecurityFilterChain, создать его можно из экземпляра HttpSecurity, который зарегистрирован в контексте приложения после включения поддержки Spring Security.
@Configuration
@EnableWebSecurity
class SecurityBeans {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// стандартные настройки цепочки безопасности
return http
// разрешить доступ только для аутентифицированных пользователей
.authorizeHttpRequests(requests -> requests.anyRequest().authenticated())
// включить поддержку формы входа
.formLogin(withDefaults())
// включить поддержку Basic-аутентификации
.httpBasic(withDefaults())
// собрать цепочку фильтров
.build();
}
}
По умолчанию в Spring Boot цепочка фильтров безопасности сконфигурирована с поддержкой Basic-аутентификации и аутентификации при помощи формы.
Если требуется сконфигурировать несколько цепочек фильтров безопасности, то их нужно регистрировать в контексте приложения с разными приоритетами:
@Configuration
@EnableWebSecurity
class SecurityBeans {
@Bean
@Order(0)
SecurityFilterChain securityFilterChain1(HttpSecurity http) throws Exception {
return http
// будет обрабатывать только запросы, начинающиеся с /api
.securityMatcher("/api/**")
// настройки цепочки безопасности
.build();
}
@Bean
@Order(1)
SecurityFilterChain securityFilterChain2(HttpSecurity http) throws Exception {
return http
// будет обрабатывать только запросы, начинающиеся с /public
.securityMatcher("/public/**")
// настройки цепочки безопасности
.build();
}
}
В следующих статьях я буду более подробно разбирать нюансы настройки цепочек фильтров безопасности, а пока вы можете ознакомиться с роликами, посвящёнными Spring Security.