without default filters, how and what will come of it

In short, the article as a reminder to me, and maybe you, how to quickly start setting up security chains, what is already connected and how to turn off everything that is not needed, quickly start writing from scratch without skins, more control and understanding.


Introduction

When solving a test task when access to a resource is not provided to everyone, I chose Spring Security to help myself: use ready-made, maybe see code that can teach me something, better understand the topic of identity and granting rights (authentication, authorization). It seemed uncomfortable to me when looking at examples on the net, so many examples, I leave uncontrolled what is now connected and what is not. This is at least included extra functionality. I tried to master the configuration of what is already connected by default.

Gradle dependencies
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'
}

Setting

I never met such information anywhere in one piece, I had to work manually, by trial and error. So, there are two ways Sprig offers to configure security through code (you can also use the configuration file): this is through inheritance to the class WebSecurityConfigurerAdapter (deprecated since version 5.7), or through the configuration class. Both are recommended to be annotated.

‘@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

which also includes the well-known annotation @Configuration and @EnableGlobalAuthentication (marks that the class can be used to construct an instance AuthenticationManagerBuilder – the builder of what the filters in question here use).

It turned out that everything that is already included is 11 filters, everything except for can be easily turned off or reconfigured, except for WebAsyncManagerIntegrationFilter. Here are some details about each of them.

Filters enabled by default:
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

If all these filters are disabled, and the resource server with the controller is already configured, then all resources will be available for all requests.

Security configuration code that turns off each filter that can be turned off:
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();
    }
}

You can also disable it with the deprecated (as the Spring documentation puts it: no lambdas, because it’s less convenient) call: .anonymous().disable().and()

As you can see, the disabled and custom classes sometimes don’t even remotely resemble the name of the security chain setup method names (see the code and comments, I deliberately placed the setup methods and custom filters in the same order). That’s not all, when you want to set up secure access to some resources, but leave at least one resource (for registration, for example) with access for everyone, it turns out that you will need AnonymousAuthenticationFilter. It also seems strange that some of the builder methods do not match any filter: .userDetailsService() and .portMapper(). You can get used to the nuances, but time! ..

Filters, no matter how many there are, compose the “chain of responsibility” pattern indirectly recursively call each other: standard – according to the list, added (by us) – in place of springSecurityFilterChain, according to the value of their order:

Some filters predefined by Sprig and their serial numbers

package

Class

serial number

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

Each filter inside springSecurityFilterChain has its own order. The initial one is set inside the HttpSecurity instance of the class variable FilterOrderRegistration, in its constructor. And you either override an existing filter or add a new one, specifying where it should be placed relative to the rest.

Controller

Now let’s add a simple controller

Controller code
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class Controller {

    @GetMapping
    public String get() {
        return "Hello!";
    }
}

Tests

…and check

Test code
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());
    }
}

Everything is working.


By the way, for self-acquaintance, you can use the advice from the Spring documentation:

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

In the processing chain

/* 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, его собственно мы и настраиваем
*/

Similar Posts

Leave a Reply