Zwei-Faktor-Authentifizierung mit Spring Security

Zwei-Faktor-Authentifizierung mit Frühlingssicherheit

1. Überblick

In diesem Tutorial werden wirTwo Factor Authentication functionality mit einem Soft Token und Spring Security implementieren.

Wir werden die neue Funktionalität inan existing, simple login flow einfügen und dieGoogle Authenticator app verwenden, um die Token zu generieren.

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

Daher stellen Benutzer während der Authentifizierung ein zusätzliches „Bestätigungstoken“ bereit - einen Code zur Überprüfung des Einmalkennworts, der auf dem Algorithmus des zeitbasierten EinmalkennwortsTOTPbasiert.

2. Maven-Konfiguration

Um Google Authenticator in unserer App verwenden zu können, müssen wir zunächst folgende Schritte ausführen:

  • Generieren Sie einen geheimen Schlüssel

  • Geben Sie dem Benutzer den geheimen Schlüssel per QR-Code

  • Überprüfen Sie das vom Benutzer eingegebene Token mit diesem geheimen Schlüssel.

Wir werden ein einfaches serverseitigeslibrary verwenden, um ein Einmalkennwort zu generieren / zu überprüfen, indem wir unserenpom.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 so, dass sie zusätzliche Informationen enthält - wie folgt:

@Entity
public class User {
    ...
    private boolean isUsing2FA;
    private String secret;

    public User() {
        super();
        this.secret = Base32.random();
        ...
    }
}

Beachten Sie, dass:

  • Wir speichern einen zufälligen Geheimcode für jeden Benutzer, der später zur Generierung 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. Wir können dies erreichen, indem wir benutzerdefinierteAuthenticationDetailsSource verwenden:

Hier sind unsereCustomWebAuthenticationDetailsSource:

@Component
public class CustomWebAuthenticationDetailsSource implements
  AuthenticationDetailsSource {

    @Override
    public WebAuthenticationDetails buildDetails(HttpServletRequest context) {
        return new CustomWebAuthenticationDetails(context);
    }
}

und hier istCustomWebAuthenticationDetails:

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 benutzerdefiniertenAuthenticationDetailsSource in unserer Sicherheitskonfiguration festlegen.

5. Benutzerdefinierter Authentifizierungsanbieter

Als Nächstes benötigen wir ein benutzerdefiniertesAuthenticationProvider, 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 nach der Überprüfung des Einmalkennwort-Überprüfungscodes die Authentifizierung einfach nachgeschaltet haben.

Hier ist unser Authentifizierungsanbieter 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 zweistufige Überprüfung entschieden haben,scan the QR-code they need to login later verwenden können.

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

Use Two step verification 

In unserenRegistrationController leiten wir Benutzer nach Bestätigung der Registrierung basierend auf ihren Auswahlmöglichkeiten weiter:

@RequestMapping(value = "/registrationConfirm", method = RequestMethod.GET)
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 MethodegenerateQRUrl():

public static String QR_PREFIX =
  "https://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 sind unsereqrcode.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 MethodegenerateQRUrl()wird verwendet, um eine QR-Code-URL zu generieren

  • Dieser QR-Code wird von den Mobiltelefonen der Nutzer mithilfe der Google Authenticator-App gescannt

  • Die App generiert einen 6-stelligen Code, der nur 30 Sekunden lang gültig ist. Dies ist der gewünschte Bestätigungscode

  • Dieser Bestätigungscode wird beim Anmelden mit unseren benutzerdefiniertenAuthenticationProviderü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:

@RequestMapping(value = "/user/update/2fa", method = RequestMethod.POST)
@ResponseBody
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 istupdateUser2FA():

@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

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 kann - wie immer -over on GitHub gefunden werden.