Коротко, статья как напоминание мне, а может и вам, как быстро начать настраивать цепочки безопасности, что уже висит подключенным и как выключить все, что не нужно, быстро с нуля начать писать без обвесок, больше контроля и понимания.
Введение
Решая тестовое задание, когда доступ к ресурсу предоставляется не всем, я выбрал 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
FilterChainProxyis 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/
Добавить комментарий