Recently, while experimenting with Spring Boot, I decided to implement an authentication flow. I started by reading documentation and watching tutorials. While these resources were helpful, I often encountered "blind spots" when it came to understanding how Spring Security works under the hood.
In this brief article, I’ll explain how Spring Security works and guide you through the steps to set up an authentication flow in less than a day.
Step 1: Choose the Right Libraries
First, you need to add the necessary dependencies. In our case, we’ll use Spring Boot Security Starter and Java JWT.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
For handling JSON Web Tokens (JWTs), we’ll later add dependencies for JJWT.
Step 2: Configure Spring Security
With the dependencies installed, we can configure Spring Security. Here’s a diagram of the key components involved:
To get started, create a new class annotated with @Configuration
. In this class, we’ll define a few essential @Bean
methods, including:
- PasswordEncoder: For securely storing passwords.
- SecurityFilterChain: To define the security filter logic.
- AuthenticationManager: To handle authentication processes.
PasswordEncoder
This @Bean
ensures that passwords are securely hashed before being stored in the database.
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
SecurityFilterChain
This @Bean
allows you to define security rules for incoming HTTP requests, such as authorization checks and filter chains.
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http.csrf().disable()
.authorizeRequests(auth -> auth
.antMatchers("/login", "/register").permitAll()
.anyRequest().authenticated())
.httpBasic().and()
.build();
}
AuthenticationManager
The AuthenticationManager
coordinates authentication by delegating the process to configured AuthenticationProvider
instances.
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
return config.getAuthenticationManager();
}
Step 3: Implement UserDetailsService
Now that the security configuration is set up, the next step is to implement the UserDetailsService
. But why is this necessary?
The UserDetailsService
is responsible for retrieving user details from the database, allowing us to verify credentials during authentication.
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
return new org.springframework.security.core.userdetails.User(
user.getUsername(),
user.getPassword(),
new ArrayList<>());
}
}
Step 4: Add JWT Utilities
With the basics in place, it’s time to configure JWT handling. For this, we’ll use the JJWT library. Add the following dependencies:
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.12.6</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.12.6</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.12.6</version>
<scope>runtime</scope>
</dependency>
Example: JWT Utility Class
@Component
public class JwtUtils {
private final String jwtSecret = "yourSecretKey";
private final int jwtExpirationMs = 86400000;
public String generateJwtToken(String username) {
return Jwts.builder()
.setSubject(username)
.setIssuedAt(new Date())
.setExpiration(new Date((new Date()).getTime() + jwtExpirationMs))
.signWith(SignatureAlgorithm.HS512, jwtSecret)
.compact();
}
public boolean validateJwtToken(String token) {
try {
Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token);
return true;
} catch (Exception e) {
return false;
}
}
}
Best Practices
-
Secure sensitive keys: Use environment variables or a secrets manager for your
jwtSecret
. - Hash passwords securely: Always use a strong hashing algorithm like BCrypt.
- Test your configuration: Use tools like Postman or cURL to ensure everything works as expected.
- Implement role-based access control: Customize access based on user roles.
Common Pitfalls
- Disabling CSRF inappropriately: Make sure to disable CSRF only for stateless APIs.
-
Using plaintext passwords: Never store plaintext passwords; always use a
PasswordEncoder
. - Hardcoding sensitive values: Avoid hardcoding secrets like the JWT key.
Conclusion and Next Steps
By following these steps, you can set up a complete authentication flow in a day. Spring Security’s powerful features might seem overwhelming at first, but with a structured approach, you can harness its capabilities effectively.
For further learning, consider:
- Adding refresh tokens to your flow.
- Exploring role-based access control with Spring Security.
- Integrating OAuth2 for more advanced authentication flows.
Top comments (0)