61 lines
2.9 KiB
Java
61 lines
2.9 KiB
Java
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);
|
|
}
|
|
}
|