Chcę dodać uwierzytelnianie wieloskładnikowe za pomocą miękkich tokenów TOTP do aplikacji Angular & Spring, jednocześnie utrzymując wszystko tak blisko, jak to możliwe, do domyślnych ustawień Spring Boot Security Starter .
Sprawdzanie poprawności tokenów odbywa się lokalnie (z biblioteką aerogear-otp-java), bez zewnętrznego dostawcy API.
Konfigurowanie tokenów dla użytkownika działa, ale sprawdzanie ich poprawności za pomocą Menedżera / dostawców uwierzytelniania Spring Security nie działa.
TL; DR
- Jaki jest oficjalny sposób zintegrowania dodatkowego dostawcy uwierzytelniania z systemem skonfigurowanym w Spring Boot Security Starter ?
- Jakie są zalecane sposoby zapobiegania atakom powtórkowym?
Długa wersja
Interfejs API ma punkt końcowy, /auth/token
z którego interfejs użytkownika może uzyskać token JWT, podając nazwę użytkownika i hasło. Odpowiedź zawiera również status uwierzytelnienia, który może mieć wartość AUTHENTICATED lub PRE_AUTHENTICATED_MFA_REQUIRED .
Jeśli użytkownik wymaga MFA, token jest wydawany z pojedynczym przyznanym uprawnieniem PRE_AUTHENTICATED_MFA_REQUIRED
i czasem wygaśnięcia wynoszącym 5 minut. Dzięki temu użytkownik może uzyskać dostęp do punktu końcowego, w /auth/mfa-token
którym może podać kod TOTP z aplikacji Authenticator i uzyskać w pełni uwierzytelniony token w celu uzyskania dostępu do witryny.
Dostawca i token
Stworzyłem swój zwyczaj, MfaAuthenticationProvider
który implementuje AuthenticationProvider
:
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
// validate the OTP code
}
@Override
public boolean supports(Class<?> authentication) {
return OneTimePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}
I OneTimePasswordAuthenticationToken
która rozciąga AbstractAuthenticationToken
się na nazwę użytkownika (wziętą z podpisanego JWT) i kodu OTP.
Config
Mam swój zwyczaj WebSecurityConfigurerAdapter
, do którego dodaję swój zwyczaj AuthenticationProvider
przez http.authenticationProvider()
. Zgodnie z JavaDoc wydaje się to właściwe miejsce:
Umożliwia dodanie dodatkowego dostawcy uwierzytelnienia, który będzie używany
Odpowiednie części mojego SecurityConfig
wygląda tak.
@Configuration
@EnableWebSecurity
@EnableJpaAuditing(auditorAwareRef = "appSecurityAuditorAware")
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final TokenProvider tokenProvider;
public SecurityConfig(TokenProvider tokenProvider) {
this.tokenProvider = tokenProvider;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authenticationProvider(new MfaAuthenticationProvider());
http.authorizeRequests()
// Public endpoints, HTML, Assets, Error Pages and Login
.antMatchers("/", "favicon.ico", "/asset/**", "/pages/**", "/api/auth/token").permitAll()
// MFA auth endpoint
.antMatchers("/api/auth/mfa-token").hasAuthority(ROLE_PRE_AUTH_MFA_REQUIRED)
// much more config
Kontroler
AuthController
Ma AuthenticationManagerBuilder
wstrzykiwany i ciągnie to wszystko razem.
@RestController
@RequestMapping(AUTH)
public class AuthController {
private final TokenProvider tokenProvider;
private final AuthenticationManagerBuilder authenticationManagerBuilder;
public AuthController(TokenProvider tokenProvider, AuthenticationManagerBuilder authenticationManagerBuilder) {
this.tokenProvider = tokenProvider;
this.authenticationManagerBuilder = authenticationManagerBuilder;
}
@PostMapping("/mfa-token")
public ResponseEntity<Token> mfaToken(@Valid @RequestBody OneTimePassword oneTimePassword) {
var username = SecurityUtils.getCurrentUserLogin().orElse("");
var authenticationToken = new OneTimePasswordAuthenticationToken(username, oneTimePassword.getCode());
var authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
// rest of class
Jednak wysyłanie wiadomości przeciw /auth/mfa-token
prowadzi do tego błędu:
"error": "Forbidden",
"message": "Access Denied",
"trace": "org.springframework.security.authentication.ProviderNotFoundException: No AuthenticationProvider found for de.....OneTimePasswordAuthenticationToken
Dlaczego Spring Security nie odbiera mojego dostawcy uwierzytelniania? Debugowanie kontrolera pokazuje, że DaoAuthenticationProvider
jest to jedyny dostawca uwierzytelniania w AuthenticationProviderManager
.
Jeśli ujawnię moją MfaAuthenticationProvider
fasolę, jest to jedyny zarejestrowany dostawca, więc otrzymam odwrotność:
No AuthenticationProvider found for org.springframework.security.authentication.UsernamePasswordAuthenticationToken.
Jak mogę uzyskać oba?
Moje pytanie
Jaki jest zalecany sposób zintegrowania dodatkowej części AuthenticationProvider
ze skonfigurowanym systemem Spring Boot Security Starter , aby uzyskać zarówno DaoAuthenticationProvider
niestandardowe , jak i własne MfaAuthenticationProvider
? Chcę zachować domyślne ustawienia Spring Boot Scurity Starter i dodatkowo mieć własnego dostawcę.
Zapobieganie atakowi powtórkowemu
Wiem, że algorytm OTP sam w sobie nie chroni przed atakami powtórkowymi w przedziale czasowym, w którym kod jest prawidłowy; RFC 6238 wyjaśnia to
Weryfikator NIE MOŻE zaakceptować drugiej próby OTP po wydaniu udanej walidacji dla pierwszego OTP, co zapewnia jednorazowe użycie OTP.
Zastanawiałem się, czy istnieje zalecany sposób wdrożenia ochrony. Ponieważ tokeny OTP są oparte na czasie, myślę o przechowywaniu ostatniego udanego logowania w modelu użytkownika i upewnieniu się, że jest tylko jedno udane logowanie na 30-sekundowy przedział czasu. To oczywiście oznacza synchronizację w modelu użytkownika. Jakieś lepsze podejścia?
Dziękuję Ci.
-
PS: ponieważ jest to pytanie dotyczące bezpieczeństwa, szukam odpowiedzi pochodzącej z wiarygodnych i / lub oficjalnych źródeł. Dziękuję Ci.