Introduction
Security filters play a crucial role in Spring Security by intercepting and processing HTTP requests before they reach your application’s endpoints. They are used for various security-related tasks such as authentication, authorization, logging, and request modification. This tutorial will guide you through the process of working with security filters in Spring Security, including creating custom filters and configuring them in your application.
Prerequisites
Before starting, ensure you have the following installed:
- JDK 8 or later
- Maven or Gradle
- An IDE like IntelliJ IDEA or Eclipse
- Basic knowledge of Spring Boot and Spring Security
Step 1: Set Up Your Spring Boot Project
First, create a new Spring Boot project. You can use Spring Initializr (https://start.spring.io/) to generate the project. Select the following dependencies:
- Spring Web
- Spring Security
Download the project and import it into your IDE.
Step 2: Basic Spring Security Configuration
Start by creating a basic Spring Security configuration. Create a configuration class in src/main/java/com/example/demo/config/SecurityConfig.java
:
package com.example.demo.config;
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.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/public/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
Step 3: Creating a Custom Security Filter
Next, create a custom security filter. This filter will log each incoming request. Create a class in src/main/java/com/example/demo/filter/LoggingFilter.java
:
package com.example.demo.filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class LoggingFilter extends HttpFilter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// Filter initialization code if necessary
}
@Override
protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
System.out.println("Request URL: " + request.getRequestURL());
chain.doFilter(request, response);
}
@Override
public void destroy() {
// Filter cleanup code if necessary
}
}
Step 4: Registering the Custom Filter
To register the custom filter, modify the security configuration class to include the filter. Update SecurityConfig
as follows:
package com.example.demo.config;
import com.example.demo.filter.LoggingFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
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.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
@Order(1)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilterBefore(new LoggingFilter(), UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.antMatchers("/public/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
Here, the addFilterBefore
method is used to add the LoggingFilter
before the UsernamePasswordAuthenticationFilter
.
Step 5: Testing the Custom Filter
Create simple controllers and views to test the custom filter. Create a controller in src/main/java/com/example/demo/controller/HomeController.java
:
package com.example.demo.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class HomeController {
@GetMapping("/")
public String home() {
return "home";
}
@GetMapping("/public")
public String publicPage() {
return "public";
}
@GetMapping("/login")
public String login() {
return "login";
}
}
Create simple Thymeleaf templates for the views in src/main/resources/templates/
.
home.html
:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Home</title>
</head>
<body>
<h1>Home Page</h1>
<a href="/public">Public Page</a><br>
<a href="/logout">Logout</a>
</body>
</html>
public.html
:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Public Page</title>
</head>
<body>
<h1>Public Page</h1>
<a href="/">Home</a>
</body>
</html>
login.html
:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Login</title>
</head>
<body>
<h1>Login</h1>
<form th:action="@{/login}" method="post">
<div>
<label for="username">Username:</label>
<input type="text" id="username" name="username"/>
</div>
<div>
<label for="password">Password:</label>
<input type="password" id="password" name="password"/>
</div>
<div>
<button type="submit">Login</button>
</div>
</form>
</body>
</html>
Run your Spring Boot application and navigate to http://localhost:8080
. Check the console output to see the log messages from your custom filter.
Step 6: Advanced Filter Configuration
You can also create filters that handle authentication and authorization more specifically. For instance, you can create a filter that checks for a specific header or token.
Example: Custom Authentication Filter
Create a custom authentication filter in src/main/java/com/example/demo/filter/CustomAuthenticationFilter.java
:
package com.example.demo.filter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
import javax.servlet.http.HttpServletRequest;
public class CustomAuthenticationFilter extends AbstractPreAuthenticatedProcessingFilter {
@Override
protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
// Extract the principal (e.g., user) from the request, such as from a header
return request.getHeader("X-Auth-User");
}
@Override
protected Object getPreAuthenticatedCredentials(HttpServletRequest request) {
// Return a dummy credential since this is pre-authenticated
return "N/A";
}
}
Registering the Custom Authentication Filter
Update the security configuration to register the custom authentication filter:
package com.example.demo.config;
import com.example.demo.filter.CustomAuthenticationFilter;
import com.example.demo.filter.LoggingFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
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.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider;
@Configuration
@EnableWebSecurity
@Order(1)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilterBefore(new LoggingFilter(), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(customAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.antMatchers("/public/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public CustomAuthenticationFilter customAuthenticationFilter() throws Exception {
CustomAuthenticationFilter filter = new CustomAuthenticationFilter();
filter.setAuthenticationManager(authenticationManager());
return filter;
}
@Bean
public PreAuthenticatedAuthenticationProvider preAuthenticatedAuthenticationProvider() {
PreAuthenticatedAuthenticationProvider provider = new PreAuthenticatedAuthenticationProvider();
provider.setPreAuthenticatedUserDetailsService(userDetailsServiceWrapper());
return provider;
}
@
Bean
public UserDetailsByNameServiceWrapper<PreAuthenticatedAuthenticationToken> userDetailsServiceWrapper() {
return new UserDetailsByNameServiceWrapper<>(userDetailsService());
}
}
Conclusion
In this tutorial, you have learned how to work with security filters in Spring Security. You created a custom logging filter, registered it in the security configuration, and explored the creation of a custom authentication filter. Filters are powerful tools in Spring Security that allow you to process requests in a flexible and efficient manner, enhancing the security and functionality of your application.