Skip to content

Filters Implementation#

Filters Implementation#

  • We can create filters by implementing the Filter interface from the javax.servlet package.
Filter.java
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package javax.servlet;

import java.io.IOException;

/**
 * A filter is an object that performs filtering tasks on either the request to
 * a resource (a servlet or static content), or on the response from a resource,
 * or both. <br>
 * <br>
 * Filters perform filtering in the <code>doFilter</code> method. Every Filter
 * has access to a FilterConfig object from which it can obtain its
 * initialization parameters, a reference to the ServletContext which it can
 * use, for example, to load resources needed for filtering tasks.
 * <p>
 * Filters are configured in the deployment descriptor of a web application
 * <p>
 * Examples that have been identified for this design are<br>
 * 1) Authentication Filters <br>
 * 2) Logging and Auditing Filters <br>
 * 3) Image conversion Filters <br>
 * 4) Data compression Filters <br>
 * 5) Encryption Filters <br>
 * 6) Tokenizing Filters <br>
 * 7) Filters that trigger resource access events <br>
 * 8) XSL/T filters <br>
 * 9) Mime-type chain Filter <br>
 *
 * @since Servlet 2.3
 */
public interface Filter {

    /**
     * Called by the web container to indicate to a filter that it is being
     * placed into service. The servlet container calls the init method exactly
     * once after instantiating the filter. The init method must complete
     * successfully before the filter is asked to do any filtering work.
     * <p>
     * The web container cannot place the filter into service if the init method
     * either:
     * <ul>
     * <li>Throws a ServletException</li>
     * <li>Does not return within a time period defined by the web
     *     container</li>
     * </ul>
     * The default implementation is a NO-OP.
     *
     * @param filterConfig The configuration information associated with the
     *                     filter instance being initialised
     *
     * @throws ServletException if the initialisation fails
     */
    public default void init(FilterConfig filterConfig) throws ServletException {}

    /**
     * The <code>doFilter</code> method of the Filter is called by the container
     * each time a request/response pair is passed through the chain due to a
     * client request for a resource at the end of the chain. The FilterChain
     * passed in to this method allows the Filter to pass on the request and
     * response to the next entity in the chain.
     * <p>
     * A typical implementation of this method would follow the following
     * pattern:- <br>
     * 1. Examine the request<br>
     * 2. Optionally wrap the request object with a custom implementation to
     * filter content or headers for input filtering <br>
     * 3. Optionally wrap the response object with a custom implementation to
     * filter content or headers for output filtering <br>
     * 4. a) <strong>Either</strong> invoke the next entity in the chain using
     * the FilterChain object (<code>chain.doFilter()</code>), <br>
     * 4. b) <strong>or</strong> not pass on the request/response pair to the
     * next entity in the filter chain to block the request processing<br>
     * 5. Directly set headers on the response after invocation of the next
     * entity in the filter chain.
     *
     * @param request  The request to process
     * @param response The response associated with the request
     * @param chain    Provides access to the next filter in the chain for this
     *                 filter to pass the request and response to for further
     *                 processing
     *
     * @throws IOException if an I/O error occurs during this filter's
     *                     processing of the request
     * @throws ServletException if the processing fails for any other reason
     */
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException;

    /**
     * Called by the web container to indicate to a filter that it is being
     * taken out of service. This method is only called once all threads within
     * the filter's doFilter method have exited or after a timeout period has
     * passed. After the web container calls this method, it will not call the
     * doFilter method again on this instance of the filter. <br>
     * <br>
     *
     * This method gives the filter an opportunity to clean up any resources
     * that are being held (for example, memory, file handles, threads) and make
     * sure that any persistent state is synchronized with the filter's current
     * state in memory.
     *
     * The default implementation is a NO-OP.
     */
    public default void destroy() {}
}
  • So you will see there are three methods, init(), doFilter() and destroy().
  • The method init() is used for adding configurations that you want the web container should run first when the filter is instantiating. The method destroy() is used for adding configurations or clean up resources when the web container indicate to a filter that it is being taken out of service. Both methods init() and destroy() are on executed once, when a filter is initiating or destroyed.

  • The method doFilter() is used for applying our business logic for the filter. The filter will be called by the container each time a request/response pair is passed through the filter chain.

  • When implementing the Filter interface we will need to override the doFilter() method to have our own custom logic. This method receives as parameters the ServletRequest, ServletResponse and FilterChain.
Parameters Descriptions
ServletRequest It represents the HTTP request. We use the ServletRequest object to retrieve details about the request from the client
ServletResponse It represents the HTTP response. We use the ServletResponse object to modify the response before sending it back to the client or further along the filter chain.
FilterChain The filter chain represents a collection of filters with a defined order in which they act. We use the FilterChain object to forward the request to the next filter in the chain.
  • You can add a new filter to the spring security chain either before, after, or at the position of a know one. Each position of the filter is an index (a number), and you might find it also referred to as the order.
  • Below are the methods available to configure a custom filter in the spring security flow.
Methods Descriptions
addFilterBefore(filter, class) adds a filter before the position of the specified filter class.
addFilterAfter(filter, class) adds a filter after the position of the specified filter class
addFilterAt(filter, class) adds a filter at the location of the specified filter class

Add Filter Before#

  • So we will use the method addFilterBefore(filter, class) to add a filter before the position of the specific filter class.
  • We will add a custom filter before the BasicAuthenticationFilter with name RequestValidationBeforeFilter for our validations where the input email provided should be the email string which match the email pattern.
  • An simple email pattern require the email which must have 3 parts: username, @ and domain.

 #zoom

Create Filter#

  • We will create a class name RequestValidationBeforeFilter and implement the interface Filter of package javax.servlet.
  • Then we will override the method doFilter of this interface as the code below.
RequestValidationBeforeFilter.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package com.spring.security.spring.security.filters.filter;

import org.springframework.stereotype.Component;
import org.springframework.util.Base64Utils;
import org.springframework.util.StringUtils;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.regex.Pattern;

@Component
public class RequestValidationBeforeFilter implements Filter {

    private static final String AUTHORIZATION_HEADER = "Authorization";
    private static final String AUTHENTICATION_SCHEMA_BASIC = "Basic";
    private static final String STRICT_EMAIL_PATTERN = "^(?=.{1,64}@)[A-Za-z0-9_-]+(\\.[A-Za-z0-9_-]+)*@"
            + "[^-][A-Za-z0-9-]+(\\.[A-Za-z0-9-]+)*(\\.[A-Za-z]{2,})$";

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        String authorizationHeader = request.getHeader(AUTHORIZATION_HEADER);
        if (StringUtils.startsWithIgnoreCase(authorizationHeader, AUTHENTICATION_SCHEMA_BASIC)) {
            byte[] base64Token = authorizationHeader.substring(6).getBytes(StandardCharsets.UTF_8);
            String decodedToken = new String(Base64Utils.decode(base64Token));
            String email = decodedToken.substring(0, decodedToken.indexOf(":"));
            if (!this.patternMatches(email)) {
                response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
                return;
            }
        }
        filterChain.doFilter(servletRequest, servletResponse);
    }

    private boolean patternMatches(String emailAddress) {
        return Pattern.compile(STRICT_EMAIL_PATTERN)
                .matcher(emailAddress)
                .matches();
    }
}
  • So in the method doFilter(), we will try to get the header by name through method getHeader() of HttpServletRequest which extended the ServletRequest.
  • Then when we got the Authorization header's value, we will continue to check this value is the Basic Authentication or not. The header's value of Basic Authentication will look like below.
1
Basic aGFuLmRvQGV4YW1wbGUuY29tOjEyMzQ1
  • If the value is not the Basic Authentication then we will forward the request and response to the next filters by method filterChain.doFilter(servletRequest, servletResponse);. If the value is the Basic Authentication then we will decode Base64 string and get the email. Below is the example decoded Base64 string.
1
han.do@example.com:12345
  • Then after we get the email from the decoded string, we will check this email string with the email pattern, so if this is not correct with the email pattern then we will set Bad Request in the HttpServletResponse and return, we don't need to forward the request and response to next filters.

Configuration#

  • After created the custom filter, we need to configure spring security for adding this custom filter before the BasicAuthenticationFilter. So let's go to the ProjectSecurityConfig class and add the configuration as below.
ProjectSecurityConfig.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
package com.spring.security.spring.security.filters.config;

import com.spring.security.spring.security.filters.filter.RequestValidationBeforeFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import java.util.Collections;

@Configuration
public class ProjectSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private RequestValidationBeforeFilter requestValidationBeforeFilter;

    /**
     *
     * contact: Not Secure
     * notice: Not Secure
     * balance: Secure
     * Card: Secure
     * Loan: Secure
     * Account: Secure
     *
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.csrf()
                .ignoringAntMatchers("/v1/user")
                .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
                .and().cors()
                .and().addFilterBefore(requestValidationBeforeFilter, BasicAuthenticationFilter.class)
                .authorizeRequests()
                .antMatchers("/v1/user").hasAnyRole("USER", "ADMIN")
                .antMatchers("/v1/accounts/**").hasRole("USER")
                .antMatchers("/v1/balance").hasRole("USER")
                .antMatchers("/v1/loan").hasRole("ADMIN")
                .antMatchers("/v1/card").hasRole("ADMIN")
                .antMatchers("/v1/contact").permitAll()
                .antMatchers("/v1/notice").permitAll()
                .and().formLogin()
                .and().httpBasic();
    }

    @Bean
    protected CorsConfigurationSource corsConfigurationSource() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.setAllowedHeaders(Collections.singletonList("*"));
        corsConfiguration.setAllowedOriginPatterns(Collections.singletonList("*"));
        corsConfiguration.setAllowedMethods(Collections.singletonList("*"));
        corsConfiguration.setAllowCredentials(true);
        source.registerCorsConfiguration("/**", corsConfiguration);
        return source;
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

}
  • As you can see, we will use the method addFilterBefore() in which the first param is our custom filter RequestValidationBeforeFilter and the second param is the filter class BasicAuthenticationFilter.class that we want to put our custom filter before.

Testing#

  • Now, let's start our spring security application and call an example api with the incorrect email pattern han.doexample.com to check. Then you should received 400 status, Bad Request in the response.

 #zoom

  • Then, if you look into the IDE logs, you will see your custom filter RequestValidationBeforeFilter had been added before the BasicAuthenticationFilter filter
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
************************************************************

Request received for GET '/v1/loan':

org.apache.catalina.connector.RequestFacade@4c974202

servletPath:/v1/loan
pathInfo:null
headers: 
authorization: Basic aGFuLmRvZXhhbXBsZS5jb206MTIzNDU=
user-agent: PostmanRuntime/7.29.2
accept: */*
postman-token: 1b348b07-0eaf-4496-baab-154b9a24a8d5
host: localhost:8080
accept-encoding: gzip, deflate, br
connection: keep-alive
cookie: JSESSIONID=27349488C0435189094D752C8D2BBFC1; XSRF-TOKEN=30a82878-5827-4b90-a340-2c090b2d16e7


Security filter chain: [
  WebAsyncManagerIntegrationFilter
  SecurityContextPersistenceFilter
  HeaderWriterFilter
  CorsFilter
  CsrfFilter
  LogoutFilter
  UsernamePasswordAuthenticationFilter
  DefaultLoginPageGeneratingFilter
  DefaultLogoutPageGeneratingFilter
  RequestValidationBeforeFilter
  BasicAuthenticationFilter
  RequestCacheAwareFilter
  SecurityContextHolderAwareRequestFilter
  AnonymousAuthenticationFilter
  SessionManagementFilter
  ExceptionTranslationFilter
  FilterSecurityInterceptor
]


************************************************************
  • Now, we will continue testing with the correct email pattern han.do@example.com, then we should see the status 200 OK.

 #zoom

Add Filter After#

  • So we will use the method addFilterAfter(filter, class) to add a filter after the position of the specific filter class.
  • We will add a custom filter after the BasicAuthenticationFilter with name AuthoritiesLoggingAfterFilter for our spring security. So after the BasicAuthenticationFilter successfully, we will try to log out the email and roles of user.

 #zoom

Create Filter#

  • Like the way we created the Before filter, we will create a class name AuthoritiesLoggingAfterFilter and implement the interface Filter of package javax.servlet.
  • Then we will override the method doFilter of this interface as the code below.
AuthoritiesLoggingAfterFilter.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.spring.security.spring.security.filters.filter;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;

import javax.servlet.*;
import java.io.IOException;
import java.util.Objects;

@Component
public class AuthoritiesLoggingAfterFilter implements Filter {

    private final Logger LOGGER = LoggerFactory.getLogger(AuthoritiesLoggingAfterFilter.class);

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (Objects.nonNull(authentication)) {
            LOGGER.info("User with email: " + authentication.getName() + "log in successfully with authorities " + authentication.getAuthorities());
        }
        filterChain.doFilter(servletRequest, servletResponse);
    }
}
  • In this filter we will try to get the Authentication in the SecurityContextHolder and log out the username and roles after authentication successfully.

Configuration#

  • After created the custom filter, we need to configure spring security for adding this custom filter after the BasicAuthenticationFilter. So let's go to the ProjectSecurityConfig class and add the configuration as below.
ProjectSecurityConfig.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
package com.spring.security.spring.security.filters.config;

import com.spring.security.spring.security.filters.filter.AuthoritiesLoggingAfterFilter;
import com.spring.security.spring.security.filters.filter.RequestValidationBeforeFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import java.util.Collections;

@Configuration
public class ProjectSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private AuthoritiesLoggingAfterFilter authoritiesLoggingAfterFilter;
    @Autowired
    private RequestValidationBeforeFilter requestValidationBeforeFilter;

    /**
     *
     * contact: Not Secure
     * notice: Not Secure
     * balance: Secure
     * Card: Secure
     * Loan: Secure
     * Account: Secure
     *
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.csrf()
                .ignoringAntMatchers("/v1/user")
                .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
                .and().cors()
                .and().addFilterBefore(requestValidationBeforeFilter, BasicAuthenticationFilter.class)
                .addFilterAfter(authoritiesLoggingAfterFilter, BasicAuthenticationFilter.class)
                .authorizeRequests()
                .antMatchers("/v1/user").hasAnyRole("USER", "ADMIN")
                .antMatchers("/v1/accounts/**").hasRole("USER")
                .antMatchers("/v1/balance").hasRole("USER")
                .antMatchers("/v1/loan").hasRole("ADMIN")
                .antMatchers("/v1/card").hasRole("ADMIN")
                .antMatchers("/v1/contact").permitAll()
                .antMatchers("/v1/notice").permitAll()
                .and().formLogin()
                .and().httpBasic();
    }

    @Bean
    protected CorsConfigurationSource corsConfigurationSource() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.setAllowedHeaders(Collections.singletonList("*"));
        corsConfiguration.setAllowedOriginPatterns(Collections.singletonList("*"));
        corsConfiguration.setAllowedMethods(Collections.singletonList("*"));
        corsConfiguration.setAllowCredentials(true);
        source.registerCorsConfiguration("/**", corsConfiguration);
        return source;
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

}
  • As you can see, we will use the method addFilterAfter() in which the first param is our custom filter AuthoritiesLoggingAfterFilter and the second param is the filter class BasicAuthenticationFilter.class that we want to put our custom filter after.

Testing#

  • Now, let's start our spring security application and call an example api. Then, if you look into the IDE logs, you will see our custom filter AuthoritiesLoggingAfterFilter had been added after the BasicAuthenticationFilter filter.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
************************************************************

Request received for GET '/v1/loan':

org.apache.catalina.connector.RequestFacade@479241a0

servletPath:/v1/loan
pathInfo:null
headers: 
authorization: Basic aGFuLmRvQGV4YW1wbGUuY29tOjEyMzQ1
user-agent: PostmanRuntime/7.29.2
accept: */*
postman-token: dd75b516-0603-4a3b-a85e-29056f4f9105
host: localhost:8080
accept-encoding: gzip, deflate, br
connection: keep-alive


Security filter chain: [
  WebAsyncManagerIntegrationFilter
  SecurityContextPersistenceFilter
  HeaderWriterFilter
  CorsFilter
  CsrfFilter
  LogoutFilter
  UsernamePasswordAuthenticationFilter
  DefaultLoginPageGeneratingFilter
  DefaultLogoutPageGeneratingFilter
  RequestValidationBeforeFilter
  BasicAuthenticationFilter
  AuthoritiesLoggingAfterFilter
  RequestCacheAwareFilter
  SecurityContextHolderAwareRequestFilter
  AnonymousAuthenticationFilter
  SessionManagementFilter
  ExceptionTranslationFilter
  FilterSecurityInterceptor
]


************************************************************
  • You also see the log in the IDE of the AuthoritiesLoggingAfterFilter with content.
1
2022-07-31 14:32:16.069  INFO 8348 --- [nio-8080-exec-1] .c.b.s.s.f.AuthoritiesLoggingAfterFilter : User with email: han.do@example.comlog in successfully with authorities [ROLE_ADMIN, ROLE_USER]

 #zoom

  • So, it means after the BasicAuthenticationFilter, so spring security had validated and stored the user's Authentication in the spring security context and you can get it to log in the AuthoritiesLoggingAfterFilter.

Add Filter At#

  • We will use the method addFilterAt(filter, class) to add a filter at the location of the specified filter class. But the order of the execution can't be guaranteed. This will not replace the filters already present at the same order. Since we will not have control on the order of the filters and it is random in nature we should avoid providing the filters at some order.

 #zoom

  • We will add a custom filter at the BasicAuthenticationFilter with name AuthoritiesLoggingAtFilter for our spring security. So when the request go to the BasicAuthenticationFilter our filter can be triggered before/after BasicAuthenticationFilter, we will try to log out the a message to make sure our custom filter is triggered.

Create Filter#

  • Like the way we created the After filter, we will create a class name AuthoritiesLoggingAtFilter and implement the interface Filter of package javax.servlet.
  • Then we will override the method doFilter of this interface as the code below.
AuthoritiesLoggingAtFilter.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
package com.spring.security.spring.security.filters.filter;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.servlet.*;
import java.io.IOException;

@Component
public class AuthoritiesLoggingAtFilter implements Filter {

    private final Logger LOGGER = LoggerFactory.getLogger(AuthoritiesLoggingAtFilter.class);

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        LOGGER.info("Authentication Validation is in progress!");
        filterChain.doFilter(servletRequest, servletResponse);
    }
}
  • In the method doFilter(), we just simply log out the message to make sure this filter will be called when our spring security application received a request.

Configuration#

  • After created the custom filter, we need to configure spring security for adding this custom filter after the BasicAuthenticationFilter. So let's go to the ProjectSecurityConfig class and add the configuration as below.
ProjectSecurityConfig.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
package com.spring.security.spring.security.filters.config;

import com.spring.security.spring.security.filters.filter.AuthoritiesLoggingAfterFilter;
import com.spring.security.spring.security.filters.filter.AuthoritiesLoggingAtFilter;
import com.spring.security.spring.security.filters.filter.RequestValidationBeforeFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import java.util.Collections;

@Configuration
public class ProjectSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private AuthoritiesLoggingAfterFilter authoritiesLoggingAfterFilter;
    @Autowired
    private AuthoritiesLoggingAtFilter authoritiesLoggingAtFilter;
    @Autowired
    private RequestValidationBeforeFilter requestValidationBeforeFilter;

    /**
     *
     * contact: Not Secure
     * notice: Not Secure
     * balance: Secure
     * Card: Secure
     * Loan: Secure
     * Account: Secure
     *
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.csrf()
                .ignoringAntMatchers("/v1/user")
                .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
                .and().cors()
                .and().addFilterBefore(requestValidationBeforeFilter, BasicAuthenticationFilter.class)
                .addFilterAfter(authoritiesLoggingAfterFilter, BasicAuthenticationFilter.class)
                .addFilterAt(authoritiesLoggingAtFilter, BasicAuthenticationFilter.class)
                .authorizeRequests()
                .antMatchers("/v1/user").hasAnyRole("USER", "ADMIN")
                .antMatchers("/v1/accounts/**").hasRole("USER")
                .antMatchers("/v1/balance").hasRole("USER")
                .antMatchers("/v1/loan").hasRole("ADMIN")
                .antMatchers("/v1/card").hasRole("ADMIN")
                .antMatchers("/v1/contact").permitAll()
                .antMatchers("/v1/notice").permitAll()
                .and().formLogin()
                .and().httpBasic();
    }

    @Bean
    protected CorsConfigurationSource corsConfigurationSource() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.setAllowedHeaders(Collections.singletonList("*"));
        corsConfiguration.setAllowedOriginPatterns(Collections.singletonList("*"));
        corsConfiguration.setAllowedMethods(Collections.singletonList("*"));
        corsConfiguration.setAllowCredentials(true);
        return source;
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

}
  • As you can see, we will use the method addFilterAt() in which the first param is our custom filter AuthoritiesLoggingAtFilter and the second param is the filter class BasicAuthenticationFilter.class that we want to put our custom filter at.

Testing#

  • Now, let's start our spring security application and call an example api. Then, if you look into the IDE logs, you will see our custom filter AuthoritiesLoggingAtFilter had been added before/after the BasicAuthenticationFilter filter. However, both filters AuthoritiesLoggingAtFilter and BasicAuthenticationFilter are always after the filter RequestValidationBeforeFilter and before the filter AuthoritiesLoggingAfterFilter.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
************************************************************

Request received for GET '/v1/loan':

org.apache.catalina.connector.RequestFacade@1e322782

servletPath:/v1/loan
pathInfo:null
headers: 
authorization: Basic aGFuLmRvQGV4YW1wbGUuY29tOjEyMzQ1
user-agent: PostmanRuntime/7.29.2
accept: */*
postman-token: 647b3d19-4336-4a58-8a66-ab51c98f19d8
host: localhost:8080
accept-encoding: gzip, deflate, br
connection: keep-alive
cookie: JSESSIONID=3036A9F01B3D6959313A0817BFEA2DE0; XSRF-TOKEN=79e2e5cb-f57a-4ea1-9b90-a96a18e52735


Security filter chain: [
  WebAsyncManagerIntegrationFilter
  SecurityContextPersistenceFilter
  HeaderWriterFilter
  CorsFilter
  CsrfFilter
  LogoutFilter
  UsernamePasswordAuthenticationFilter
  DefaultLoginPageGeneratingFilter
  DefaultLogoutPageGeneratingFilter
  RequestValidationBeforeFilter
  AuthoritiesLoggingAtFilter
  BasicAuthenticationFilter
  AuthoritiesLoggingAfterFilter
  RequestCacheAwareFilter
  SecurityContextHolderAwareRequestFilter
  AnonymousAuthenticationFilter
  SessionManagementFilter
  ExceptionTranslationFilter
  FilterSecurityInterceptor
]


************************************************************
  • You can also see the log details that you put into the AuthoritiesLoggingAtFilter in the image below.

 #zoom

See Also#

References#