Stop Hand‑Coding Login Checks: One Spring Boot 4.0 Annotation Enables Full MFA
The article explains why relying solely on username‑password authentication is risky, introduces multi‑factor authentication (MFA) as the only effective solution, and shows how Spring Boot 4.0’s @EnableMultiFactorAuthentication annotation together with Spring Security 7 lets developers implement MFA with just an annotation, automatic factor tracking, smart redirects, and minimal configuration.
Why password‑only authentication is insufficient
The 2025 RockYou data set contains nearly 10 billion real passwords, making brute‑force attacks a practical threat. Passwords therefore act only as an identifier, not as a defensive barrier.
Multi‑Factor Authentication (MFA)
OWASP classifies authentication factors into five categories:
Knowledge – something you know (password, PIN)
Possession – something you have (phone, email)
Biometric – something you are (fingerprint, face)
Location – something you are at (geographic position)
Behavior – something you do (typing rhythm)
The most cost‑effective combination is knowledge (password) plus possession (one‑time token). The typical flow is:
User submits username and password.
System generates a one‑time token.
Token is sent to the user’s phone or email.
User enters the token to complete login.
Spring Boot 4.0 “killer feature” for MFA
Spring Boot 4.0 (built on Spring Security 7) introduces a single annotation that becomes the control hub for the entire MFA system: @EnableMultiFactorAuthentication This annotation solves three historic pain points:
Automatic factor‑state tracking – the framework records which factors the current user has completed.
Zero‑configuration smart redirects – the framework decides which factor is missing and which authentication page to show.
Factor‑level authority management – each factor has its own FactorGrantedAuthority (e.g., FactorGrantedAuthority.PASSWORD_AUTHORITY, FactorGrantedAuthority.OTT_AUTHORITY).
Full‑stack demonstration: Pig Mall backend MFA login
Project dependencies
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>4.0.1</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
</dependencies>Security configuration
package com.icoderoad.pig.security;
@Configuration
@EnableWebSecurity
@EnableMultiFactorAuthentication(authorities = {
FactorGrantedAuthority.PASSWORD_AUTHORITY,
FactorGrantedAuthority.OTT_AUTHORITY
})
public class PigSecurityConfig {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/", "/ott/sent").permitAll()
.requestMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.formLogin(withDefaults()) // password factor
.oneTimeTokenLogin(withDefaults()) // one‑time token factor
.build();
}
@Bean
UserDetailsService userDetailsService() {
var admin = User.withUsername("lengleng")
.password("{noop}pig123")
.roles("ADMIN", "USER")
.build();
var guest = User.withUsername("guest")
.password("{noop}guest")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(admin, guest);
}
}Core interpretation
@EnableMultiFactorAuthenticationdeclares which authentication factors must be satisfied. formLogin() + oneTimeTokenLogin() correspond to the password and OTP factors respectively.
No additional session‑management code is required.
Business controller example
package com.icoderoad.pig.web;
@RestController
public class PigAdminController {
@GetMapping("/")
public String home() { return "Pig 商城首页"; }
@GetMapping("/admin")
public String admin() { return "Pig 管理后台 - 欢迎 lengleng"; }
@GetMapping("/ott/sent")
public String ottSent() { return "验证码已发送,请查收"; }
}Custom one‑time token service (5‑digit PIN)
package com.icoderoad.pig.security.ott;
public class PigPinTokenService implements OneTimeTokenService {
private static final int PIN_LENGTH = 5;
private static final int MAX_PIN = 100_000;
private final Map<String, OneTimeToken> tokens = new ConcurrentHashMap<>();
private final SecureRandom random = new SecureRandom();
private Duration expiresIn = Duration.ofMinutes(5);
@Override
public OneTimeToken generate(GenerateOneTimeTokenRequest request) {
String pin = String.format("%05d", random.nextInt(MAX_PIN));
Instant expiresAt = Instant.now().plus(expiresIn);
OneTimeToken token = new DefaultOneTimeToken(pin, request.getUsername(), expiresAt);
tokens.put(pin, token);
cleanup();
return token;
}
@Override
public OneTimeToken consume(OneTimeTokenAuthenticationToken authToken) {
OneTimeToken token = tokens.remove(authToken.getTokenValue());
return token != null && Instant.now().isBefore(token.getExpiresAt()) ? token : null;
}
private void cleanup() {
if (tokens.size() < 100) return;
Instant now = Instant.now();
tokens.entrySet().removeIf(e -> now.isAfter(e.getValue().getExpiresAt()));
}
public void setExpiresIn(Duration expiresIn) { this.expiresIn = expiresIn; }
}Bean registration
@Bean
public OneTimeTokenService oneTimeTokenService() {
PigPinTokenService service = new PigPinTokenService();
service.setExpiresIn(Duration.ofMinutes(3));
return service;
}Automatic redirect mechanism
When a required factor is missing, Spring Security adds request parameters such as factor.type=OTT and factor.reason=MISSING. The front‑end can read these parameters and display the appropriate OTP page without any business‑logic code.
Advanced usage: endpoint‑level MFA control
var mfa = AuthorizationManagerFactories.multiFactor()
.requireFactors(FactorGrantedAuthority.PASSWORD_AUTHORITY,
FactorGrantedAuthority.OTT_AUTHORITY)
.build();
http.authorizeHttpRequests(auth -> auth
.requestMatchers("/admin/**").access(mfa.hasRole("ADMIN"))
.requestMatchers("/user/**").authenticated()
.anyRequest().permitAll()
);Password validity duration
var passwordIn30m = AuthorizationManagerFactories.multiFactor()
.requireFactor(f -> f.passwordAuthority()
.validDuration(Duration.ofMinutes(30)))
.build();Production‑ready considerations
Token storage: use Redis or JDBC instead of in‑memory storage for real deployments.
Token TTL: 3–5 minutes balances security and user experience.
Delivery method: send OTP via email or SMS; never log tokens to the console.
Debug mode: enable @EnableWebSecurity(debug = true) and set logging level to org.springframework.security: TRACE for troubleshooting.
Number of factors: two factors provide strong security with acceptable UX; adding a third often degrades usability.
Conclusion
Spring Boot 4.0’s @EnableMultiFactorAuthentication reduces MFA implementation from hundreds of lines of custom code to a single annotation, two lines of filter configuration, and a modest token‑service implementation, making strong authentication accessible to ordinary projects.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
LuTiao Programming
LuTiao Programming is a friendly community offering free programming lessons. We inspire learners to explore new ideas and technologies and quickly acquire job-ready skills.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
