Skip to main content

Spring Security Configuration

Security is a critical aspect of application development, and Spring Security makes it easier to implement robust security measures. In this guide, we'll explore how to configure Spring Security for your Spring applications, from basic setup to more advanced configurations.

Introduction to Spring Security Configuration

Spring Security is a powerful framework that provides authentication, authorization, and protection against common security vulnerabilities. Before diving into code, it's important to understand that Spring Security configuration has evolved over time:

  1. XML-based configuration (older approach)
  2. Java-based configuration (recommended for most applications)
  3. Method-level security annotations
  4. OAuth2/JWT configurations for modern applications

We'll focus primarily on Java-based configuration in this guide as it's the most common approach today.

Adding Spring Security to Your Project

To get started, you need to add Spring Security to your project dependencies.

For Maven:

xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>

For Gradle:

groovy
implementation 'org.springframework.boot:spring-boot-starter-security'

Basic Configuration

Once you add the dependency, Spring Boot automatically configures basic security with:

  • Form-based login
  • HTTP Basic authentication
  • CSRF protection
  • Session fixation protection
  • Security headers

Let's create a simple security configuration:

java
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.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/public/**").permitAll()
.anyRequest().authenticated()
)
.formLogin(form -> form
.loginPage("/login")
.permitAll()
)
.logout(logout -> logout
.permitAll()
);

return http.build();
}
}

This configuration:

  1. Allows unrestricted access to URLs under /public/
  2. Requires authentication for all other requests
  3. Sets up a custom login page at /login
  4. Configures logout functionality

User Authentication Configuration

Spring Security needs to know how to authenticate users. Let's configure in-memory authentication for simplicity:

java
import org.springframework.context.annotation.Bean;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;

@Configuration
public class UserConfig {

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

@Bean
public InMemoryUserDetailsManager userDetailsService() {
UserDetails user = User.builder()
.username("user")
.password(passwordEncoder().encode("password"))
.roles("USER")
.build();

UserDetails admin = User.builder()
.username("admin")
.password(passwordEncoder().encode("admin"))
.roles("USER", "ADMIN")
.build();

return new InMemoryUserDetailsManager(user, admin);
}
}

Note: In-memory authentication is only suitable for development and testing. For production applications, you should use database-backed authentication.

Database Authentication Configuration

For real-world applications, you'll want to use database authentication:

java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.core.userdetails.UserDetailsService;
import javax.sql.DataSource;

@Configuration
public class DatabaseAuthConfig {

@Autowired
private DataSource dataSource;

@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication()
.dataSource(dataSource)
.usersByUsernameQuery("select username, password, enabled from users where username = ?")
.authoritiesByUsernameQuery("select username, authority from authorities where username = ?")
.passwordEncoder(passwordEncoder());
}

// Or using JPA with UserDetailsService
@Autowired
private UserDetailsService userDetailsService;

@Autowired
public void configureAuthentication(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
}

Authorization Configuration

Authorization determines what authenticated users can do. Spring Security provides several ways to configure authorization:

URL-Based Authorization

java
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/", "/home").permitAll()
.requestMatchers("/admin/**").hasRole("ADMIN")
.requestMatchers("/user/**").hasAnyRole("USER", "ADMIN")
.requestMatchers("/api/**").hasAuthority("API_ACCESS")
.anyRequest().authenticated()
);

return http.build();
}

Method-Level Security

First, enable method security:

java
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;

@Configuration
@EnableMethodSecurity(securedEnabled = true, jsr250Enabled = true)
public class MethodSecurityConfig {
// Configuration
}

Then use annotations in your service classes:

java
import org.springframework.security.access.prepost.PreAuthorize;

@Service
public class UserService {

@PreAuthorize("hasRole('ADMIN')")
public List<User> getAllUsers() {
// Only accessible to admins
return userRepository.findAll();
}

@PreAuthorize("hasRole('USER') or #username == authentication.principal.username")
public User getUserByUsername(String username) {
// Accessible to admins or the user themselves
return userRepository.findByUsername(username);
}
}

Customizing Form Login

Spring Security's default login form is functional but basic. Here's how to customize it:

java
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.formLogin(form -> form
.loginPage("/custom-login")
.loginProcessingUrl("/perform-login")
.defaultSuccessUrl("/dashboard", true)
.failureUrl("/custom-login?error=true")
.usernameParameter("username")
.passwordParameter("password")
.permitAll()
);

return http.build();
}

Then create your custom login page:

html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org">
<head>
<title>Custom Login</title>
</head>
<body>
<h2>Please Login</h2>
<div th:if="${param.error}">
Invalid username or password.
</div>
<div th:if="${param.logout}">
You have been logged out.
</div>
<form th:action="@{/perform-login}" method="post">
<div>
<label>Username: <input type="text" name="username"/></label>
</div>
<div>
<label>Password: <input type="password" name="password"/></label>
</div>
<div>
<input type="submit" value="Log in"/>
</div>
</form>
</body>
</html>

CSRF Protection

Cross-Site Request Forgery (CSRF) protection is enabled by default. For REST APIs, you might want to disable it:

java
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable());

return http.build();
}

For standard web applications, you need to include a CSRF token in your forms:

html
<form th:action="@{/process}" method="post">
<!-- Thymeleaf automatically includes the CSRF token -->
<input type="text" name="data"/>
<button type="submit">Submit</button>
</form>

For JavaScript applications:

javascript
// Using Fetch API
fetch('/api/resource', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="_csrf"]').content
},
body: JSON.stringify(data)
})

Handling CORS

For web applications that need to handle Cross-Origin Resource Sharing:

java
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.cors(cors -> cors.configurationSource(corsConfigurationSource()));

return http.build();
}

@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("https://example.com"));
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE"));
configuration.setAllowCredentials(true);
configuration.setAllowedHeaders(Arrays.asList("Authorization", "Cache-Control", "Content-Type"));

UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}

OAuth2 and JWT Configuration

For modern applications, JWT (JSON Web Tokens) authentication is common:

java
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/public/**").permitAll()
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2 -> oauth2.jwt());

return http.build();
}

@Bean
public JwtDecoder jwtDecoder() {
return NimbusJwtDecoder.withJwkSetUri("https://auth-provider/.well-known/jwks.json").build();
}

Real-world Example: Complete Web Application Security Configuration

Here's a comprehensive example combining multiple security features:

java
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.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class WebSecurityConfig {

@Autowired
private DataSource dataSource;

@Autowired
private CustomUserDetailsService userDetailsService;

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

@Bean
public PersistentTokenRepository persistentTokenRepository() {
JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
tokenRepository.setDataSource(dataSource);
return tokenRepository;
}

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/", "/home", "/register", "/css/**", "/js/**", "/images/**").permitAll()
.requestMatchers("/admin/**").hasRole("ADMIN")
.requestMatchers("/api/**").hasAuthority("API_ACCESS")
.anyRequest().authenticated()
)
.formLogin(form -> form
.loginPage("/login")
.defaultSuccessUrl("/dashboard")
.permitAll()
)
.rememberMe(remember -> remember
.tokenRepository(persistentTokenRepository())
.tokenValiditySeconds(86400) // 1 day
.userDetailsService(userDetailsService)
)
.logout(logout -> logout
.logoutUrl("/logout")
.logoutSuccessUrl("/login?logout")
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID", "remember-me")
.permitAll()
)
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.invalidSessionUrl("/login")
.maximumSessions(1)
.maxSessionsPreventsLogin(false)
)
.headers(headers -> headers
.frameOptions().sameOrigin()
.xssProtection().block(true)
.contentSecurityPolicy("script-src 'self'")
);

return http.build();
}
}

This configuration includes:

  • URL-based authorization rules
  • Custom login page
  • Remember-me functionality using database storage
  • Logout configuration
  • Session management with restrictions
  • Security headers configuration

Summary

Spring Security offers a comprehensive framework for securing your applications. We've covered:

  1. Basic security configuration
  2. Authentication with in-memory and database providers
  3. Authorization at both URL and method levels
  4. Form login customization
  5. CSRF protection
  6. CORS configuration
  7. JWT/OAuth2 integration
  8. Real-world comprehensive example

Remember that security is not a one-time setup. Always stay updated with the latest security practices and Spring Security versions to protect your applications effectively.

Additional Resources

Exercises

  1. Create a basic Spring Security configuration that secures all endpoints except for a public landing page and login page.
  2. Implement database authentication using JPA with a custom UserDetailsService.
  3. Add method-level security that restricts certain operations to users with specific authorities.
  4. Configure a remember-me feature for your application.
  5. Implement JWT authentication for a REST API.

By working through these exercises, you'll gain practical experience with Spring Security configuration and be better equipped to secure your own applications.



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)