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


Введение

Решая тестовое задание, когда доступ к ресурсу предоставляется не всем, я выбрал 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, его собственно мы и настраиваем
*/

Комментарии (0)