Spring security: без фильтров по умолчанию, как и что из этого получится

от автора

Коротко, статья как напоминание мне, а может и вам, как быстро начать настраивать цепочки безопасности, что уже висит подключенным и как выключить все, что не нужно, быстро с нуля начать писать без обвесок, больше контроля и понимания.


Введение

Решая тестовое задание, когда доступ к ресурсу предоставляется не всем, я выбрал Spring Security, чтобы помочь себе: использолвать готовое, возможно увидеть код, который может меня чему-то научить, лучше разобраться в теме удостоверения и предоставления прав (authentication, authorization). Мне показалось неуютным, когда глядя на примеры в сети, очень многие примеры, я оставляю бесконтрольным, что сейчас подключено, а что нет. Это по меньшей мере включенный лишний функционал. Я постарался овладеть настройкой того, что уже подключено по умолчанию.

Зависимости Gradle
dependencies {   // необходимые     implementation 'org.springframework.boot:spring-boot-starter-security'     implementation 'org.springframework.boot:spring-boot-starter-web'          // для красоты, удобства и тестов     compileOnly 'org.projectlombok:lombok'     developmentOnly 'org.springframework.boot:spring-boot-devtools'     annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'     annotationProcessor 'org.projectlombok:lombok'     testImplementation 'org.springframework.boot:spring-boot-starter-test'     testImplementation 'org.springframework.security:spring-security-test' }

Настройка

Одним куском такую информацию так нигде и не встретил, пришлось поработать в ручную, пробами и ошибками. Итак, есть два предлагамых Спригом способа настройки безопасности через код (можно ещё через файл конфигурации): это через наследование классу WebSecurityConfigurerAdapter (с версии 5.7 признан устаревшим), либо через класс конфигурации. На оба рекомендовано навесить аннотацию

‘@EnableWebSecurity’,
@Retention(RetentionPolicy.RUNTIME)  @Target({ElementType.TYPE})  @Documented  @Import({org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration.class,org.springframework.security.config.annotation.web.configuration.SpringWebMvcImportSelector.class,org.springframework.security.config.annotation.web.configuration.OAuth2ImportSelector.class,org.springframework.security.config.annotation.web.configuration.HttpSecurityConfiguration.class})  @EnableGlobalAuthentication  @Configuration  public @interface EnableWebSecurity extends annotation.Annotation

которая вбирает в себя и хорошо известную аннотацию @Configuration и @EnableGlobalAuthentication (помечает, что класс может быть использован для построения экземпляра AuthenticationManagerBuilder — строитель того, что используют филтры, о которых идёт здесь речь).

Оказалось, что всё, что уже включено, это 11 фильтров, все кроме можно легко выключить или перенастроить, кроме WebAsyncManagerIntegrationFilter. Вот некоторые подробности о каждом из них.

Филтры, подключенные по умолчанию:
org.springframework.security.web.authentication.       AnonymousAuthenticationFilter org.springframework.security.web.csrf.                 CsrfFilter org.springframework.security.web.session.              DisableEncodeUrlFilter org.springframework.security.web.access.               ExceptionTranslationFilter org.springframework.security.web.header.               HeaderWriterFilter org.springframework.security.web.authentication.logout.LogoutFilter org.springframework.security.web.savedrequest.         RequestCacheAwareFilter org.springframework.security.web.servletapi.           SecurityContextHolderAwareRequestFilter org.springframework.security.web.context.              SecurityContextPersistenceFilter org.springframework.security.web.session.              SessionManagementFilter org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter

Если все эти филтры выключить, а сервер ресурсов с контроллером уже настроены, то все ресурсы будут доступны для всех запросов.

Код настройки безопасности, выключающий каждый достпуный для выключения фильтр:
import lombok.val; import org.springframework.context.annotation.Bean; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.web.SecurityFilterChain;  @EnableWebSecurity public class SecurityFilterChainImpl {     @Bean     public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {         return chain = http                 .anonymous(AbstractHttpConfigurer::disable)         // AnonymousAuthenticationFilter                 .csrf(AbstractHttpConfigurer::disable)              // CsrfFilter                 .sessionManagement(AbstractHttpConfigurer::disable) // DisableEncodeUrlFilter, SessionManagementFilter                 .exceptionHandling(AbstractHttpConfigurer::disable) // ExceptionTranslationFilter                 .headers(AbstractHttpConfigurer::disable)           // HeaderWriterFilter                 .logout(AbstractHttpConfigurer::disable)            // LogoutFilter                 .requestCache(AbstractHttpConfigurer::disable)      // RequestCacheAwareFilter                 .servletApi(AbstractHttpConfigurer::disable)        // SecurityContextHolderAwareRequestFilter                 .securityContext(AbstractHttpConfigurer::disable)   // SecurityContextPersistenceFilter                 .build();     } }

Выключить можно и устаревшим (как выражается документация Спринга: без лямбд, ведь это менее удобно) вызовом: .anonymous().disable().and()

Как видите, выключаемые и настраиваемы классы иногда даже отдалённо не напоминают названием имена методов стоителя настрое цепи безопасности (см. код и комментарии, я нарочно разместил методы настроек и настраиваемые фильтры в том же порядке). Это ещё не всё, когда захочется настроить защищённый доступ к некоторым ресурасм, но хотя бы один ресурс (для регистрации, например) оставите с досупом для всех, окажется, что понадобится AnonymousAuthenticationFilter. Ещё странным кажется, что некоторым методам строителя не соотвествует ни один фильтр: .userDetailsService() и .portMapper(). К нюансам можно привыкнуть, но время!..

Фильтры, сколько бы их ни было, слагают шаблон «цепочка ответственности» косвенно рекурсивно вызывают один другого: стандартные — по списку, добавленные (нами) — на месте springSecurityFilterChain, по значению их порядка:

Некоторые фильтры, предопределённые Спригом, и их порядковые номера

пакет

класс

порядковый номер

org.springframework.security.web.session.

DisableEncodeUrlFilter

100

org.springframework.security.web.session.

ForceEagerSessionCreationFilter

200

org.springframework.security.web.access.channel.

ChannelProcessingFilter

300

org.springframework.security.web.context.request.async.

WebAsyncManagerIntegrationFilter

500

org.springframework.security.web.context.

SecurityContextHolderFilter

600

org.springframework.security.web.context.

SecurityContextPersistenceFilter

700

org.springframework.security.web.header.

HeaderWriterFilter

800

org.springframework.web.filter.

CorsFilter

900

org.springframework.security.web.csrf.

CsrfFilter

1000

org.springframework.security.web.authentication.logout.

LogoutFilter

1100

org.springframework.security.oauth2.client.web.

OAuth2AuthorizationRequestRedirectFilter

1200

org.springframework.security.saml2.provider.service.servlet.filter.

Saml2WebSsoAuthenticationRequestFilter

1300

org.springframework.security.web.authentication.preauth.x509.

X509AuthenticationFilter

1400

org.springframework.security.web.authentication.preauth.

AbstractPreAuthenticatedProcessingFilter

1500

org.springframework.security.cas.web.

CasAuthenticationFilter

1600

org.springframework.security.oauth2.client.web.

OAuth2LoginAuthenticationFilter

1700

org.springframework.security.saml2.provider.service.servlet.filter.

Saml2WebSsoAuthenticationFilter

1800

org.springframework.security.web.authentication.

UsernamePasswordAuthenticationFilter

1900

org.springframework.security.openid.

OpenIDAuthenticationFilter

2100

org.springframework.security.web.authentication.ui.

DefaultLoginPageGeneratingFilter

2200

org.springframework.security.web.authentication.ui.

DefaultLogoutPageGeneratingFilter

2300

org.springframework.security.web.session.

ConcurrentSessionFilter

2400

org.springframework.security.web.authentication.www.

DigestAuthenticationFilter

2500

org.springframework.security.oauth2.server.resource.web.

BearerTokenAuthenticationFilter

2600

org.springframework.security.web.authentication.www.

BasicAuthenticationFilter

2700

org.springframework.security.web.savedrequest.

RequestCacheAwareFilter

2800

org.springframework.security.web.servletapi.

SecurityContextHolderAwareRequestFilter

2900

org.springframework.security.web.jaasapi.

JaasApiIntegrationFilter

3000

org.springframework.security.web.authentication.rememberme.

RememberMeAuthenticationFilter

3100

org.springframework.security.web.authentication.

AnonymousAuthenticationFilter

3200

org.springframework.security.oauth2.client.web.

OAuth2AuthorizationCodeGrantFilter

3300

org.springframework.security.web.session.

SessionManagementFilter

3400

org.springframework.security.web.access.

ExceptionTranslationFilter

3500

org.springframework.security.web.access.intercept.

FilterSecurityInterceptor

3600

org.springframework.security.web.access.intercept.

AuthorizationFilter

3700

org.springframework.security.web.authentication.switchuser.

SwitchUserFilter

3800

Каждый фильтр внутри springSecurityFilterChain имеет свой порядок. Начальный задается внутри экземпляра HttpSecurity переменной класса FilterOrderRegistration, в её конструкторе. И вы либо переопределяете существующий фильтр, либо добавляете новый, указывая где он должен быть размещён, относительно остальных.

Контроллер

Теперь добавим простенький контроллер

Код контроллера
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController;  @RestController public class Controller {      @GetMapping     public String get() {         return "Hello!";     } }

Тесты

…И проверим

Код теста
import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest;  import java.io.IOException; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse;  import static org.junit.jupiter.api.Assertions.assertEquals;  @SpringBootTest public class ControllerIT {      @Test     public void test() throws IOException, InterruptedException {         final HttpRequest request = HttpRequest.newBuilder()                 .uri(URI.create("http://localhost:8080"))                 .GET()                 .build();         final HttpClient client = HttpClient.newHttpClient();          final HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());          assertEquals("Hello!", response.body());     } } 

Всё работает.


Кстати, для самостоятельного ознакомления, можно воспользоваться советом из документации Спринга:

…adding a debug point in FilterChainProxy is a great place to start.

В цепи обработки

/* FilterChainProxy#doFilter(ServletRequest request, ServletResponse response, FilterChain chain)):  переменная chain#filters содержит  0 = {ApplicationFilterConfig@7248} "ApplicationFilterConfig[name=characterEncodingFilter, filterClass=org.springframework.boot.web.servlet.filter.OrderedCharacterEncodingFilter]" 1 = {ApplicationFilterConfig@7249} "ApplicationFilterConfig[name=formContentFilter, filterClass=org.springframework.boot.web.servlet.filter.OrderedFormContentFilter]" 2 = {ApplicationFilterConfig@7250} "ApplicationFilterConfig[name=requestContextFilter, filterClass=org.springframework.boot.web.servlet.filter.OrderedRequestContextFilter]" 3 = {ApplicationFilterConfig@7251} "ApplicationFilterConfig[name=springSecurityFilterChain, filterClass=org.springframework.boot.web.servlet.DelegatingFilterProxyRegistrationBean$1]" 4 = {ApplicationFilterConfig@7252} "ApplicationFilterConfig[name=Tomcat WebSocket (JSR356) Filter, filterClass=org.apache.tomcat.websocket.server.WsFilter]" springSecurityFilterChain, его собственно мы и настраиваем */


ссылка на оригинал статьи https://habr.com/ru/post/680052/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *