Added 2FA

This commit is contained in:
2026-08-08 15:09:26 +02:00
parent b5de4d6ac7
commit 9f311ab4ae
19 changed files with 1341 additions and 1 deletions
@@ -0,0 +1,60 @@
package com.example.security;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* Wraps the standard username/password check with a downgrade step: if the authenticating user
* has two-factor authentication enabled, the returned token carries only {@link #PRE_AUTH_ROLE}
* instead of their real authorities.
* <p>
* This is the enforcement mechanism for 2FA — a session holding only that role satisfies no
* {@code @PermitAll}/{@code @RolesAllowed} check on real application views, no matter what the
* navigation layer does. {@link TwoFactorNavigationGuard} is the UX layer on top: it notices the
* downgraded role and routes the user to the code-entry view. {@link TwoFactorService#verify}
* plus {@link TwoFactorService#completeAuthentication} is what upgrades the session once the
* correct code is supplied.
*/
@Component
class TwoFactorAwareAuthenticationProvider implements AuthenticationProvider {
// Deliberately not in the Role enum: that enum models roles persisted per-user, while this
// is a transient authentication state no user is ever granted.
static final String PRE_AUTH_ROLE = "PRE_AUTH_2FA";
static final String PRE_AUTH_AUTHORITY = "ROLE_" + PRE_AUTH_ROLE;
private final DaoAuthenticationProvider delegate;
private final TwoFactorService twoFactorService;
TwoFactorAwareAuthenticationProvider(
UserDetailsService userDetailsService, PasswordEncoder passwordEncoder, TwoFactorService twoFactorService) {
delegate = new DaoAuthenticationProvider(userDetailsService);
delegate.setPasswordEncoder(passwordEncoder);
this.twoFactorService = twoFactorService;
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
var fullyAuthenticated = delegate.authenticate(authentication);
if (fullyAuthenticated == null || !twoFactorService.isTwoFactorEnabled(fullyAuthenticated.getName())) {
return fullyAuthenticated;
}
return UsernamePasswordAuthenticationToken.authenticated(
fullyAuthenticated.getPrincipal(), fullyAuthenticated.getCredentials(),
List.of(new SimpleGrantedAuthority(PRE_AUTH_AUTHORITY)));
}
@Override
public boolean supports(Class<?> authentication) {
return delegate.supports(authentication);
}
}