Write and Publish a Tutorial!
Do you have good notes or papers written by you and seeking for a
platform to publish? We provide the platform to publish your tutorials
in your name. If you wish to publish your tutorial in your name to
help the readers, Please contact us by sending an email to
publish@tools4testing.com or publish@java4coding.com The main way that
others learn about your work is through your published tutorials. If
you don’t publish, it will be as if you never did the work. Your notes
can help the readers only when you share it.
How to create custom CsrfTokenRepository in Spring Security
By default, Spring Security stores CSRF (Cross-Site Request Forgery) tokens in the HTTP session on the server side. While suitable for smaller applications, this approach becomes less efficient for larger applications needing horizontal scalability due to the stateful nature of HTTP sessions.
To customize CSRF token management, Spring Security provides two key contracts: CsrfToken and CsrfTokenRepository.
CsrfToken: This interface describes the CSRF token itself. It includes three main characteristics: the name of the header in the request containing the token value (default: X-CSRF-TOKEN), the name of the attribute of the request storing the token value (default: _csrf), and the token value itself. An example implementation is DefaultCsrfToken.
CsrfTokenRepository: Responsible for managing CSRF tokens within Spring Security, this interface defines methods for creating, storing, and loading CSRF tokens. Implementing this interface allows developers to customize token management, such as storing tokens in a database rather than the HTTP session.
In a custom implementation, CSRF tokens might be stored in a database. Clients are assumed to have unique identifiers for token retrieval and validation, akin to session IDs. Alternatively, tokens may have defined lifetimes, expiring after a specified duration, without being tied to specific user IDs. This approach simplifies token validation by checking token existence and expiration for each incoming request.
Example Implementation
CREATE TABLE IF NOT EXISTS TOKEN ( `id` INT NOT NULL AUTO_INCREMENT, `identifier` VARCHAR(45) NULL, `token` TEXT NULL, PRIMARY KEY (`id`) ); |
package com.java4coding.security;
import com.java4coding.entity.Token; import com.java4coding.repository.JpaTokenRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.web.csrf.CsrfToken; import org.springframework.security.web.csrf.CsrfTokenRepository; import org.springframework.security.web.csrf.DefaultCsrfToken;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.Optional; import java.util.UUID;
public class CustomCsrfTokenRepository implements CsrfTokenRepository { @Autowired private JpaTokenRepository jpaTokenRepository;
@Override public CsrfToken generateToken(HttpServletRequest httpServletRequest) { String uuid = UUID.randomUUID().toString(); return new DefaultCsrfToken("X-CSRF-TOKEN", "_csrf", uuid); }
@Override public void saveToken(CsrfToken csrfToken, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) { String identifier = httpServletRequest.getHeader("X-IDENTIFIER"); Optional<Token> existingToken = jpaTokenRepository.findTokenByIdentifier(identifier); if (existingToken.isPresent()) { Token token = existingToken.get(); token.setToken(csrfToken.getToken()); } else { Token token = new Token(); token.setToken(csrfToken.getToken()); token.setIdentifier(identifier); jpaTokenRepository.save(token); } }
@Override public CsrfToken loadToken(HttpServletRequest httpServletRequest) { String identifier = httpServletRequest.getHeader("X-IDENTIFIER"); Optional<Token> existingToken = jpaTokenRepository .findTokenByIdentifier(identifier); if (existingToken.isPresent()) { Token token = existingToken.get(); return new DefaultCsrfToken( "X-CSRF-TOKEN", "_csrf", token.getToken()); } return null; } } package com.java4coding.repository;
import com.java4coding.entity.Token; import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface JpaTokenRepository extends JpaRepository<Token, Integer> { Optional<Token> findTokenByIdentifier(String identifier); } package com.java4coding.entity;
import lombok.Getter; import lombok.Setter;
import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id;
@Entity @Setter @Getter public class Token { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private int id; private String identifier; private String token; } package com.java4coding.config;
import com.java4coding.security.CustomCsrfTokenRepository; 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.builders.WebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.web.csrf.CsrfTokenRepository;
@Configuration public class ApplicationWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
/* Since H2 has its own authentication provider, you can skip the Spring Security for the path of h2 console entirely in the same way that you do for your static content. In order to do that, in your Spring security config, you have to override the configuration method which takes an instance of org.springframework.security.config.annotation.web.builders.WebSecurity as a parameter instead of the one which takes an instance of org.springframework.security.config.annotation.web.builders.HttpSecurity */ @Override public void configure(WebSecurity web) throws Exception { web.ignoring().antMatchers("/h2-console/**"); }
@Bean public CsrfTokenRepository customTokenRepository() { return new CustomCsrfTokenRepository(); }
@Override protected void configure(HttpSecurity http) throws Exception { http.csrf(c -> { c.csrfTokenRepository(customTokenRepository()); c.ignoringAntMatchers("/hi"); }); http.authorizeRequests() .anyRequest().permitAll(); }
} package com.java4coding.config;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.password.NoOpPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.provisioning.InMemoryUserDetailsManager;
@Configuration public class ApplicationConfig {
@Bean public UserDetailsService userDetailsService() { var inMemoryUserDetailsManager = new InMemoryUserDetailsManager(); inMemoryUserDetailsManager.createUser(User.withUsername("manu") .password("pass") .roles("ADMIN") .build()); inMemoryUserDetailsManager.createUser(User.withUsername("advith") .password("xyz123") .roles("MANAGER") .build()); inMemoryUserDetailsManager.createUser(User.withUsername("aashvith") .password("xyz123") .roles("MANAGER") .build()); return inMemoryUserDetailsManager; }
@Bean public PasswordEncoder passwordEncoder() { return NoOpPasswordEncoder.getInstance(); } } package com.java4coding.controller;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController;
@RestController public class DemoController {
@PostMapping(value = "/hello") public String sayHelloPost() { return "Hello!"; }
@PostMapping(value = "/hi") public String sayHi() { return "Hi!"; }
} package com.java4coding;
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
@SpringBootApplication @EntityScan @EnableJpaRepositories public class SpringBootDemo { public static void main(String[] args) { SpringApplication.run(SpringBootDemo.class, args); } } |