Two Factor Auth met Spring Security

1. Overzicht

In deze tutorial gaan we Two Factor Authentication-functionaliteit implementeren met een Soft Token en Spring Security.

We gaan de nieuwe functionaliteit toevoegen aan een bestaande, eenvoudige inlogstroom en de Google Authenticator-app gebruiken om de tokens te genereren.

Simpel gezegd, tweefactorauthenticatie is een verificatieproces dat het bekende principe volgt van "iets dat de gebruiker weet en iets dat de gebruiker heeft".

En dus bieden gebruikers een extra "verificatietoken" tijdens authenticatie - een eenmalige wachtwoordverificatiecode op basis van Time-based One-time Password TOTP-algoritme.

2. Maven-configuratie

Om Google Authenticator in onze app te kunnen gebruiken, moeten we eerst:

  • Genereer een geheime sleutel
  • Geef de gebruiker een geheime sleutel via QR-code
  • Verifieer het token dat door de gebruiker is ingevoerd met deze geheime sleutel.

We zullen een eenvoudige bibliotheek aan de serverzijde gebruiken om een ​​eenmalig wachtwoord te genereren / verifiëren door de volgende afhankelijkheid toe te voegen aan onze pom.xml:

 org.jboss.aerogear aerogear-otp-java 1.0.0 

3. Gebruikersentiteit

Vervolgens zullen we onze gebruikersentiteit aanpassen om extra informatie te bewaren - als volgt:

@Entity openbare klasse Gebruiker {... private boolean isUsing2FA; privé String geheim; openbare gebruiker () {super (); this.secret = Base32.random (); ...}}

Let daar op:

  • We slaan een willekeurige geheime code op voor elke gebruiker om later te gebruiken bij het genereren van een verificatiecode
  • Onze authenticatie in twee stappen is optioneel

4. Extra aanmeldingsparameter

Ten eerste moeten we onze beveiligingsconfiguratie aanpassen om een ​​extra parameter te accepteren - verificatietoken. Dat kunnen we bereiken door gebruik te maken van maatwerk AuthenticationDetailsSource:

Hier is onze CustomWebAuthenticationDetailsSource:

@Component openbare klasse CustomWebAuthenticationDetailsSource implementeert AuthenticationDetailsSource {@Override openbare WebAuthenticationDetails buildDetails (HttpServletRequest-context) {retourneer nieuwe CustomWebAuthenticationDetails (context); }}

en hier is CustomWebAuthenticationDetails:

openbare klasse CustomWebAuthenticationDetails breidt WebAuthenticationDetails {private String verificatieCode uit; openbare CustomWebAuthenticationDetails (verzoek HttpServletRequest) {super (verzoek); verificatieCode = request.getParameter ("code"); } public String getVerificationCode () {terugkeer verificatiecode; }}

En onze beveiligingsconfiguratie:

@Configuration @EnableWebSecurity openbare klasse LssSecurityConfig breidt WebSecurityConfigurerAdapter uit {@Autowired privé CustomWebAuthenticationDetailsSource authenticationDetailsSource; @Override protected void configure (HttpSecurity http) genereert uitzondering {http.formLogin () .authenticationDetailsSource (authenticationDetailsSource) ...}}

En voeg tot slot de extra parameter toe aan ons inlogformulier:

 Verificatiecode van Google Authenticator 

Opmerking: we moeten onze gewoonte instellen AuthenticationDetailsSource in onze beveiligingsconfiguratie.

5. Aangepaste authenticatieleverancier

Vervolgens hebben we een aangepaste versie nodig AuthenticationProvider om extra parametervalidatie af te handelen:

openbare klasse CustomAuthenticationProvider breidt DaoAuthenticationProvider {@Autowired private UserRepository userRepository uit; @Override public Authentication authenticate (Authentication auth) genereert AuthenticationException {String verificatieCode = ((CustomWebAuthenticationDetails) auth.getDetails ()) .getVerificationCode (); Gebruiker user = userRepository.findByEmail (auth.getName ()); if ((user == null)) {gooi nieuwe BadCredentialsException ("Ongeldige gebruikersnaam of wachtwoord"); } if (user.isUsing2FA ()) {Totp totp = nieuwe Totp (user.getSecret ()); if (! isValidLong (verificatiecode) ||! totp.verify (verificatiecode)) {gooi nieuwe BadCredentialsException ("Ongeldige verificatiecode"); }} Authenticatieresultaat = super.authenticate (auth); retourneer nieuwe UsernamePasswordAuthenticationToken (gebruiker, resultaat.getCredentials (), resultaat.getAuthorities ()); } private boolean isValidLong (String-code) {probeer {Long.parseLong (code); } catch (NumberFormatException e) {return false; } retourneren waar; } @Override openbare booleaanse ondersteuning (Klasse-authenticatie) {retourneer authentication.equals (UsernamePasswordAuthenticationToken.class); }}

Houd er rekening mee dat - nadat we de verificatiecode voor eenmalig wachtwoord hebben geverifieerd, we de authenticatie simpelweg downstream hebben gedelegeerd.

Hier is de bean van onze authenticatieleverancier

@Bean openbaar DaoAuthenticationProvider authProvider () {CustomAuthenticationProvider authProvider = nieuwe CustomAuthenticationProvider (); authProvider.setUserDetailsService (userDetailsService); authProvider.setPasswordEncoder (encoder ()); retourneer authProvider; }

6. Registratieproces

Om ervoor te zorgen dat gebruikers de applicatie kunnen gebruiken om de tokens te genereren, moeten ze de zaken correct instellen wanneer ze zich registreren.

En dus moeten we enkele eenvoudige wijzigingen aanbrengen in het registratieproces - zodat gebruikers die ervoor hebben gekozen om authenticatie in twee stappen te gebruiken scan de QR-code die ze nodig hebben om later in te loggen.

Ten eerste voegen we deze eenvoudige invoer toe aan ons registratieformulier:

Gebruik tweestapsverificatie 

Dan, in onze RegistratieController - we verwijzen gebruikers door op basis van hun keuzes na bevestiging van de registratie:

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

En hier is onze methode genereerQRUrl ():

openbare statische String QR_PREFIX = "//chart.googleapis.com/chart?chs=200x200&chld=M%%7C0&cht=qr&chl="; @Override public String generationQRUrl (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"); }

En hier is onze qrcode.html:

Scan deze streepjescode met de Google Authenticator-app op uw telefoon om deze later bij het inloggen te gebruiken

Ga naar de inlogpagina

Let daar op:

  • genereerQRUrl () methode wordt gebruikt om een ​​QR-code URL te genereren
  • Deze QR-code wordt gescand door mobiele telefoons van gebruikers met behulp van de Google Authenticator-app
  • De app genereert een 6-cijferige code die slechts 30 seconden geldig is, wat de gewenste verificatiecode is
  • Deze verificatiecode wordt geverifieerd tijdens het inloggen met behulp van onze aangepaste AuthenticationProvider

7. Schakel tweestapsverificatie in

Vervolgens zorgen we ervoor dat gebruikers hun inlogvoorkeuren op elk moment kunnen wijzigen - als volgt:

@PostMapping ("/ user / update / 2fa") public GenericResponse adjustUser2FA (@RequestParam ("use2FA") boolean use2FA) genereert UnsupportedEncodingException {User user = userService.updateUser2FA (use2FA); if (use2FA) {retourneer nieuwe GenericResponse (userService.generateQRUrl (gebruiker)); } retourneer null; }

En hier is updateUser2FA ():

@Override openbare gebruiker updateUser2FA (boolean use2FA) {Authentication curAuth = SecurityContextHolder.getContext (). GetAuthentication (); Gebruiker currentUser = (Gebruiker) curAuth.getPrincipal (); currentUser.setUsing2FA (use2FA); currentUser = repository.save (currentUser); Authenticatie auth = nieuwe UsernamePasswordAuthenticationToken (currentUser, currentUser.getPassword (), curAuth.getAuthorities ()); SecurityContextHolder.getContext (). SetAuthentication (auth); return currentUser; }

En hier is de front-end:

 U gebruikt authenticatie in twee stappen. Schakel 2FA uit. U gebruikt geen authenticatie in twee stappen. Schakel 2FA in

Scan deze streepjescode met de Google Authenticator-app op uw telefoon

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

8. Conclusie

In deze korte zelfstudie hebben we geïllustreerd hoe u een tweefactorauthenticatie-implementatie uitvoert met behulp van een Soft Token met Spring Security.

De volledige broncode is - zoals altijd - te vinden op GitHub.