Zwei-Faktor-Authentifizierung mit Federsicherheit

1. Übersicht

In diesem Tutorial werden wir die Zwei-Faktor-Authentifizierungsfunktion mit einem Soft Token und Spring Security implementieren.

Wir werden die neue Funktionalität in einen vorhandenen, einfachen Anmeldefluss einfügen und die Token mithilfe der Google Authenticator-App generieren.

Einfach ausgedrückt ist die Zwei-Faktor-Authentifizierung ein Überprüfungsprozess, der dem bekannten Prinzip „etwas, das der Benutzer weiß und was der Benutzer hat“ folgt.

Daher stellen Benutzer während der Authentifizierung ein zusätzliches „Verifizierungstoken“ zur Verfügung - einen Einmalkennwort-Verifizierungscode, der auf dem zeitbasierten Einmalkennwort-TOTP-Algorithmus basiert.

2. Maven-Konfiguration

Um Google Authenticator in unserer App verwenden zu können, müssen wir zunächst Folgendes tun:

  • Geheimen Schlüssel generieren
  • Geben Sie dem Benutzer einen geheimen Schlüssel per QR-Code
  • Überprüfen Sie das vom Benutzer eingegebene Token mit diesem geheimen Schlüssel.

Wir werden eine einfache serverseitige Bibliothek verwenden, um ein Einmalkennwort zu generieren / zu überprüfen, indem wir unserer pom.xml die folgende Abhängigkeit hinzufügen :

 org.jboss.aerogear aerogear-otp-java 1.0.0 

3. Benutzerentität

Als Nächstes ändern wir unsere Benutzerentität wie folgt, um zusätzliche Informationen aufzunehmen:

@Entity public class User { ... private boolean isUsing2FA; private String secret; public User() { super(); this.secret = Base32.random(); ... } }

Beachten Sie, dass:

  • Wir speichern für jeden Benutzer einen zufälligen Geheimcode, der später beim Generieren des Bestätigungscodes verwendet wird
  • Unsere Bestätigung in zwei Schritten ist optional

4. Zusätzlicher Anmeldeparameter

Zuerst müssen wir unsere Sicherheitskonfiguration anpassen, um zusätzliche Parameter zu akzeptieren - Verifikationstoken. Dies können wir mithilfe der benutzerdefinierten AuthenticationDetailsSource erreichen :

Hier ist unsere CustomWebAuthenticationDetailsSource :

@Component public class CustomWebAuthenticationDetailsSource implements AuthenticationDetailsSource { @Override public WebAuthenticationDetails buildDetails(HttpServletRequest context) { return new CustomWebAuthenticationDetails(context); } }

und hier ist CustomWebAuthenticationDetails :

public class CustomWebAuthenticationDetails extends WebAuthenticationDetails { private String verificationCode; public CustomWebAuthenticationDetails(HttpServletRequest request) { super(request); verificationCode = request.getParameter("code"); } public String getVerificationCode() { return verificationCode; } }

Und unsere Sicherheitskonfiguration:

@Configuration @EnableWebSecurity public class LssSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private CustomWebAuthenticationDetailsSource authenticationDetailsSource; @Override protected void configure(HttpSecurity http) throws Exception { http.formLogin() .authenticationDetailsSource(authenticationDetailsSource) ... } }

Und schließlich fügen Sie den zusätzlichen Parameter zu unserem Anmeldeformular hinzu:

 Google Authenticator Verification Code  

Hinweis: Wir müssen unsere benutzerdefinierte AuthenticationDetailsSource in unserer Sicherheitskonfiguration festlegen .

5. Benutzerdefinierter Authentifizierungsanbieter

Als Nächstes benötigen wir einen benutzerdefinierten AuthenticationProvider , um die zusätzliche Parameterüberprüfung durchzuführen:

public class CustomAuthenticationProvider extends DaoAuthenticationProvider { @Autowired private UserRepository userRepository; @Override public Authentication authenticate(Authentication auth) throws AuthenticationException { String verificationCode = ((CustomWebAuthenticationDetails) auth.getDetails()) .getVerificationCode(); User user = userRepository.findByEmail(auth.getName()); if ((user == null)) { throw new BadCredentialsException("Invalid username or password"); } if (user.isUsing2FA()) { Totp totp = new Totp(user.getSecret()); if (!isValidLong(verificationCode) || !totp.verify(verificationCode)) { throw new BadCredentialsException("Invalid verfication code"); } } Authentication result = super.authenticate(auth); return new UsernamePasswordAuthenticationToken( user, result.getCredentials(), result.getAuthorities()); } private boolean isValidLong(String code) { try { Long.parseLong(code); } catch (NumberFormatException e) { return false; } return true; } @Override public boolean supports(Class authentication) { return authentication.equals(UsernamePasswordAuthenticationToken.class); } }

Beachten Sie, dass wir - nachdem wir den Bestätigungscode für das Einmalkennwort überprüft haben - die Authentifizierung einfach nachgelagert delegiert haben.

Hier ist unsere Authentication Provider Bean

@Bean public DaoAuthenticationProvider authProvider() { CustomAuthenticationProvider authProvider = new CustomAuthenticationProvider(); authProvider.setUserDetailsService(userDetailsService); authProvider.setPasswordEncoder(encoder()); return authProvider; }

6. Registrierungsprozess

Damit Benutzer die Anwendung zum Generieren der Token verwenden können, müssen sie die Dinge bei der Registrierung ordnungsgemäß einrichten.

Daher müssen wir einige einfache Änderungen am Registrierungsprozess vornehmen, damit Benutzer, die sich für die Bestätigung in zwei Schritten entschieden haben, den QR-Code scannen können, den sie später für die Anmeldung benötigen .

Zuerst fügen wir diese einfache Eingabe unserem Registrierungsformular hinzu:

Use Two step verification 

Anschließend leiten wir in unserem RegistrationController Benutzer nach Bestätigung der Registrierung basierend auf ihren Auswahlmöglichkeiten weiter:

@GetMapping("/registrationConfirm") public String confirmRegistration(@RequestParam("token") String token, ...) { String result = userService.validateVerificationToken(token); if(result.equals("valid")) { User user = userService.getUser(token); if (user.isUsing2FA()) { model.addAttribute("qr", userService.generateQRUrl(user)); return "redirect:/qrcode.html?lang=" + locale.getLanguage(); } model.addAttribute( "message", messages.getMessage("message.accountVerified", null, locale)); return "redirect:/login?lang=" + locale.getLanguage(); } ... }

Und hier ist unsere Methode generateQRUrl () :

public static String QR_PREFIX = "//chart.googleapis.com/chart?chs=200x200&chld=M%%7C0&cht=qr&chl="; @Override public String generateQRUrl(User user) { return QR_PREFIX + URLEncoder.encode(String.format( "otpauth://totp/%s:%s?secret=%s&issuer=%s", APP_NAME, user.getEmail(), user.getSecret(), APP_NAME), "UTF-8"); }

Und hier ist unsere qrcode.html :

Scan this Barcode using Google Authenticator app on your phone to use it later in login

Go to login page

Beachten Sie, dass:

  • Die Methode generateQRUrl () wird zum Generieren der QR-Code-URL verwendet
  • Dieser QR-Code wird von den Mobiltelefonen der Benutzer mithilfe der Google Authenticator-App gescannt
  • Die App generiert einen 6-stelligen Code, der nur 30 Sekunden gültig ist und den gewünschten Bestätigungscode enthält
  • Dieser Bestätigungscode wird bei der Anmeldung mit unserem benutzerdefinierten AuthenticationProvider überprüft

7. Aktivieren Sie die Bestätigung in zwei Schritten

Als Nächstes stellen wir sicher, dass Benutzer ihre Anmeldeeinstellungen jederzeit wie folgt ändern können:

@PostMapping("/user/update/2fa") public GenericResponse modifyUser2FA(@RequestParam("use2FA") boolean use2FA) throws UnsupportedEncodingException { User user = userService.updateUser2FA(use2FA); if (use2FA) { return new GenericResponse(userService.generateQRUrl(user)); } return null; }

Und hier ist updateUser2FA () :

@Override public User updateUser2FA(boolean use2FA) { Authentication curAuth = SecurityContextHolder.getContext().getAuthentication(); User currentUser = (User) curAuth.getPrincipal(); currentUser.setUsing2FA(use2FA); currentUser = repository.save(currentUser); Authentication auth = new UsernamePasswordAuthenticationToken( currentUser, currentUser.getPassword(), curAuth.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(auth); return currentUser; }

Und hier ist das Frontend:

 You are using Two-step authentication Disable 2FA You are not using Two-step authentication Enable 2FA

Scan this Barcode using Google Authenticator app on your phone

function enable2FA(){ set2FA(true); } function disable2FA(){ set2FA(false); } function set2FA(use2FA){ $.post( "/user/update/2fa", { use2FA: use2FA } , function( data ) { if(use2FA){ $("#qr").append('').show(); }else{ window.location.reload(); } }); }

8. Fazit

In diesem kurzen Tutorial haben wir gezeigt, wie eine Zwei-Faktor-Authentifizierungsimplementierung mit einem Soft Token mit Spring Security durchgeführt wird.

Der vollständige Quellcode ist - wie immer - auf GitHub zu finden.