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.

LuTiao Programming
LuTiao Programming
LuTiao Programming
Stop Hand‑Coding Login Checks: One Spring Boot 4.0 Annotation Enables Full MFA

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

@EnableMultiFactorAuthentication

declares 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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Spring BootauthenticationSpring SecurityMFA
LuTiao Programming
Written by

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.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.