In Spring Security, a SecurityFilterChain is a vital component that handles security-related tasks in a web application. It signifies a chain of filters applied to incoming requests to implement security measures. Every filter in the chain carries out a specific security task, like authentication, authorization, or other personalized security checks.
The HttpSecurity class is crucial while building a SecurityFilterChain. It signifies the security configuration for the web-based security of a Spring application. You use the HttpSecurity object to define how security should be applied to individual parts of your application. This includes establishing access control rules, setting up authentication methods, and configuring custom security filters.
In this topic, you will explore the HttpSecurity class and how to employ it to configure the security of a Spring application.
HttpSecurity overview
org.springframework.security.config.annotation.web.builders.HttpSecurity is a final class implementing the SecurityBuilder interface. It is used to create a org.springframework.security.web.SecurityFilterChain for a web-based Spring application. Below, we have a simplified version of the class hierarchy of the HttpSecurity class. Additionally, critical methods of the HttpSecurity class are shown. We will discuss these methods later.
The HttpSecurity class is a builder with a fluent API that allows the configuration of a Spring application's security. As you can see, it has several methods for configuring various aspects of the application's security. We'll explore these shortly.
Before looking at the HttpSecurity class in more detail, let's take a look at the org.springframework.security.config.Customizer interface. It's a generic functional interface that allows customizing the HttpSecurity object. It has a single abstract method customize(T) that receives an object of type T and returns void. The object of the type T is the configurer used to customize the HttpSecurity object. Various configurers can be used to customize the HttpSecurity object.
For example, configuring the security headers means calling the headers() method of the HttpSecurity object and passing a Customizer object to it. We can pass a Customizer object using a lambda expression. The lambda expression will receive an object of type HeadersConfigurer<HttpSecurity> and will return void. The HeadersConfigurer<HttpSecurity> object is the configurer used to customize the HttpSecurity object.
In case we do not want to make any modifications to the headers, we can use the withDefaults() method of the Customizer interface. It returns a Customizer object that does nothing. This is a nicer way than writing an empty lambda expression. Compare the following two snippets of code.
Providing an empty lambda expression:
http.headers { }Using the withDefaults():
http.headers(withDefaults())Take note that HttpSecurity methods that do not receive a Customizer object are marked as deprecated and mustn't be used.
Let's now delve into some of the key methods of the HttpSecurity class. That said, there are a lot of them, so we'll group them into categories. Here's one of the simplest forms of a method responsible for creating a security filter chain bean:
@Bean
@Throws(Exception::class) // you can omit @Throws(Exception::class) in Kotlin
//if you don't need it for interoperability with Java,
//as Kotlin doesn't require you to declare checked exceptions
fun filterChain(http: HttpSecurity): SecurityFilterChain {
return http
.authorizeHttpRequests { authorizeRequests ->
authorizeRequests.anyRequest().authenticated()
}
.httpBasic(withDefaults())
.build()
}Diverse configurations can be created using the HttpSecurity builder class. Let's now overview what this class can offer.
Adding filters
addFilter(Filter filter)addFilterAfter(Filter filter, Class<? extends Filter> afterFilter)addFilterAt(Filter filter, Class<? extends Filter> atFilter)addFilterBefore(Filter filter, Class<? extends Filter> beforeFilter)
The addFilter() method allows adding a filter that is an instance of one of the filters provided by Spring Security. The filter has to have a registered order, otherwise, an exception will be thrown.
The order is defined by the org.springframework.security.config.annotation.web.builders.FilterOrderRegistration class.
Trying to add a custom filter using this method will result in an exception, stating that the filter does not have a registered order and that it cannot be added without a specified order. To solve this problem, we would need to add the filter at a specific position in the chain using one of the methods below.
// This will not work!
http.addFilter(MyFilter())
return http.build()addFilterAfter() will add the new filter after the filter of the specified type.
addFilterAt() will add the new filter at the same position as the filter of the specified type.
addFilterBefore() adds the filter before the filter of the specified type.
Note that the addFilterAfter(), addFilterAt(), and addFilterBefore() methods use the same private method addFilterAtOffsetOf(), which is responsible for adding the filter at the specified offset. The offset is 1, 0, or -1, depending on the method used.
For example, this will add the custom filter at the position of the UsernamePasswordAuthenticationFilter:
http.addFilterAt(CustomFilter(), UsernamePasswordAuthenticationFilter::class.java)Changing the authentication manager and provider
authenticationManager(AuthenticationManager authenticationManager)authenticationProvider(AuthenticationProvider authenticationProvider)
The method authenticationManager() allows configuring the default AuthenticationManager that will be used to authenticate requests. The AuthenticationManager is responsible for authenticating the Authentication object passed to it. It delegates the authentication to the AuthenticationProvider that is configured in the AuthenticationManager.
authenticationProvider() allows configuring the AuthenticationProvider that will be used to authenticate requests. The AuthenticationProvider is responsible for authenticating the Authentication object passed to it. We learned in a previous topic about an implementation of it, the DaoAuthenticationProvider, which uses a UserDetailsService to retrieve the user details from a database.
Configuring requests authorization
authorizeHttpRequests(Customizer authorizeHttpRequestsCustomizer)
This method helps to restrict access based on the HttpServletRequest using RequestMatcher implementations (i.e., via URL patterns).
http.authorizeHttpRequests { authz ->
authz
.requestMatchers("/admin/**").hasRole("ADMIN")
.requestMatchers("/**").hasRole("USER")
.anyRequest().authenticated()
}Here, anything under /admin requires the user with ROLE_ADMIN role. Everything else requires a user with the ROLE_USER role. Moreover, all requests need successful authentication.
Using different authentication schemes
formLogin(Customizer formLoginCustomizer)httpBasic(Customizer httpBasicCustomizer)jee(Customizer jeeCustomizer)rememberMe(Customizer rememberMeCustomizer)x509(Customizer x509Customizer)
formLogin() supports form-based authentication. With the default settings, a default login page will be generated at /login, redirecting to /login?error for authentication failures.
http.formLogin(withDefaults())We can customize the defaults using:
http.formLogin { formLogin ->
formLogin
.usernameParameter("username")
.passwordParameter("password")
.loginPage("/authentication/login")
.failureUrl("/authentication/login?failed")
.loginProcessingUrl("/authentication/login/process")
} httpBasic() configures HTTP Basic authentication.
http.authorizeRequests { authorizeRequests ->
authorizeRequests
.anyRequest().authenticated()
}
.httpBasic(withDefaults())jee() configures container-based pre-authentication. In this case, authentication is managed by the Servlet Container.
rememberMe() allows configuring of Remember Me authentication. The following configuration demonstrates how to allow token based remember me authentication. Upon authenticating, if the HTTP parameter named remember-me exists, then the user will be remembered even after their jakarta.servlet.http.HttpSession expires.
http.authorizeRequests { authorizeRequests ->
authorizeRequests
.anyRequest().authenticated()
}
.rememberMe(withDefaults())
.formLogin(withDefaults()) In other words, if upon authenticating the HTTP parameter remember-me is set, the browser will get a new cookie, besides the JSESSIONID, named remember-me, which will be used to remember the authenticated user after the session has expired. We can simulate session expiration by just deleting the JSESSIONID cookie from the browser.
x509() configures X509 based pre authentication. The following configuration will attempt to extract the username from the X509 certificate.
http.authorizeRequests { authorizeRequests ->
authorizeRequests
.anyRequest().authenticated()
}
.x509(withDefaults())Unsuccessful authentications
Let's now look at what happens when the authentication attempt fails. In this case, Spring Security uses the AuthenticationEntryPoint interface to handle unsuccessful authentication attempts. When a user tries accessing a secured resource without proper authentication, Spring Security triggers an authentication failure event, and AuthenticationEntryPoint handles the response.
The primary method in the AuthenticationEntryPoint interface is:
@Throws(IOException::class, ServletException::class) // If you're writing purely Kotlin code and don't need to interoperate with
// Java that requires throws, you can omit @Throws
fun commence(
request: HttpServletRequest,
response: HttpServletResponse,
authException: AuthenticationException
) {
// Implementation here
}The method has the following parameters:
request: The HTTP request that resulted in the authentication failure.response: The HTTP response that the entry point can use to send a response to the client.authException: The exception that caused the authentication failure.
Common implementations of AuthenticationEntryPoint include:
BasicAuthenticationEntryPoint: This is used to send a Basic Authentication challenge to the client.Http403ForbiddenEntryPoint: This is used to send a 403 (Forbidden) error code to the client.LoginUrlAuthenticationEntryPoint: This is used to redirect the client to a login page.
You can see how one such entry point can be created and used in the examples below:
@Bean
fun authEntryPoint(): AuthenticationEntryPoint {
return BasicAuthenticationEntryPoint().apply {
realmName = "user realm" // Not specifying the realm results in an exception
}
}http.authorizeRequests { authorizeRequests ->
authorizeRequests
.anyRequest().authenticated()
}
.httpBasic(withDefaults())
.exceptionHandling { configurer ->
configurer.authenticationEntryPoint(authEntryPoint())
}Conclusion
In this topic, we went over the HttpSecurity class and how to use it to configure the security of a Spring application. We learned about the various methods that can be used to configure the security of the application. We also learned about the Customizer interface and how to use it to customize the HttpSecurity object.