🚀 Part 1: Spring Security Setup in Spring Boot MVC with Thymeleaf

Welcome to the first part of our series on building secure Java web apps with Spring Security! If you’re developing a modern web application, security isn’t optional — it’s essential. In this post, we’ll walk through setting up Spring Security in a Spring Boot MVC app using Thymeleaf, focusing on the core of your app’s defense: the SecurityFilterChain.

Let’s get started!

🔗 GitHub Repo: https://github.com/manueltechlabs/java-blog-project/tree/main

📦 Step 1: Add the Right Dependencies

To secure your Spring Boot app, you need the right tools. Here’s what to include in your pom.xml:

pom.xml

xml
12345678910111213141516171819202122232425
      <dependencies>
    <!-- Web & MVC -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- Thymeleaf Templates -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>

    <!-- Spring Security -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>

    <!-- Security Dialect for Thymeleaf -->
    <dependency>
        <groupId>org.thymeleaf.extras</groupId>
        <artifactId>thymeleaf-extras-springsecurity6</artifactId>
    </dependency>
</dependencies>
    
  • ✅ Why these?

spring-boot-starter-security auto-configures secure defaults. Thymeleaf extras let you use sec:authorize in HTML to show/hide UI elements based on user roles.

🔐 Step 2: Configure SecurityFilterChain

This is where the magic happens. The SecurityFilterChain defines who can access what in your app.

Here’s a clean, production-ready setup:

SecurityConfig.java

java
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354
      @Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            // Public pages
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/css/**", "/js/**", "/img/**", "/login", "/register").permitAll()
                .requestMatchers("/profile/**").authenticated()
                .requestMatchers("/admin/**").hasAuthority("ROLE_ADMIN")
                .anyRequest().permitAll()
            )
            // Login
            .formLogin(form -> form
                .loginPage("/login")
                .usernameParameter("email")
                .passwordParameter("password")
                .failureUrl("/login?error")
                .permitAll()
            )
            // Logout
            .logout(logout -> logout
                .logoutUrl("/logout")
                .logoutSuccessUrl("/")
                .deleteCookies("JSESSIONID")
                .permitAll()
            )
            // Remember-Me
            .rememberMe(remember -> remember
                .key("uniqueAndSecret")
                .tokenValiditySeconds(86400)
            )
            // Session
            .sessionManagement(session -> session
                .sessionCreationPolicy(SessionCreationPolicy.ALWAYS)
            )
            // Security Headers
            .headers(headers -> headers
                .xssProtection(xss -> xss.headerValue(ENABLED_MODE_BLOCK))
                .contentSecurityPolicy(cps -> cps
                    .policyDirectives("script-src 'self' https: 'unsafe-inline'; style-src 'self' https: 'unsafe-inline'"))
            );

        return http.build();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}
    
FeatureWhy It Matters
@EnableMethodSecurityEnables @PreAuthorize, @Secured, etc., for fine-grained control
Form LoginCustom login with email/password fields
CSRF ProtectionEnabled by default — Thymeleaf auto-includes tokens
Remember-MePersistent login (30 days)
Session ManagementFull control over session lifecycle
Security HeadersBlocks XSS and enforces safe content loading

💡 Pro Tip: Never Disable CSRF in Production

You might be tempted to add .csrf().disable() — don’t. It opens your app to attacks. Instead, ensure your forms include the CSRF token:

login.html

html
1234
      <form th:action="@{/login}" method="post">
    <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
    <!-- rest of form -->
</form>
    

Thymeleaf does this automatically when using th:action.

🧩 What’s Next?

We’ve laid the foundation. In upcoming chapters, we’ll dive into:

  • Password handling with char[] vs String
  • Role hierarchies and granular privileges
  • Secure user registration and profile management

👉 Next: Part 2 – Securing Sensitive Data: Why char[] Over String for Passwords

🔗 GitHub Repo: https://github.com/manueltechlabs/java-blog-project/tree/main