Registratie - Activeer een nieuw account via e-mail

Dit artikel maakt deel uit van een reeks: • Tutorial Spring Security Registration

• Het registratieproces met Spring Security

• Registratie - Activeer een nieuw account per e-mail (huidig ​​artikel) • Spring beveiligingsregistratie - Verzend de verificatie-e-mail opnieuw

• Registratie met Spring Security - Wachtwoordcodering

• De registratie-API wordt RESTful

• Spring Security - Reset uw wachtwoord

• Registratie - Wachtwoordsterkte en regels

• Uw wachtwoord bijwerken

1. Overzicht

Dit artikel gaat verder Registratie bij Spring Security serie met een van de ontbrekende stukjes van het registratieproces - het verifiëren van het e-mailadres van de gebruiker om zijn account te bevestigen.

Het registratiebevestigingsmechanisme dwingt de gebruiker om te reageren op een "Bevestig registratie”E-mail verzonden na succesvolle registratie om zijn e-mailadres te verifiëren en hun account te activeren. De gebruiker doet dit door op een unieke activeringslink te klikken die via e-mail naar hem is verzonden.

Door deze logica te volgen, kan een nieuw geregistreerde gebruiker niet inloggen op het systeem totdat dit proces is voltooid.

2. Een verificatietoken

We zullen een eenvoudig verificatietoken gebruiken als het belangrijkste artefact waarmee een gebruiker wordt geverifieerd.

2.1. De VerificationToken Entiteit

De VerificationToken entiteit moet aan de volgende criteria voldoen:

  1. Het moet terugverwijzen naar het Gebruiker (via een unidirectionele relatie)
  2. Het wordt direct na registratie aangemaakt
  3. Het zal verlopen binnen 24 uur na zijn oprichting
  4. Heeft een uniek, willekeurig gegenereerd waarde

Eisen 2 en 3 maken deel uit van de registratielogica. De andere twee zijn geïmplementeerd in een eenvoudig VerificationToken entiteit zoals die in Voorbeeld 2.1 .:

Voorbeeld 2.1.

@ Entity openbare klasse VerificationToken {privé statische laatste int EXPIRATION = 60 * 24; @Id @GeneratedValue (strategie = GenerationType.AUTO) privé Lange id; privé String-token; @OneToOne (targetEntity = User.class, fetch = FetchType.EAGER) @JoinColumn (nullable = false, name = "user_id") privégebruiker; privé Datum expiratiedatum; privédatum berekenExpiryDate (int expiryTimeInMinutes) {Calendar cal = Calendar.getInstance (); cal.setTime (nieuwe tijdstempel (cal.getTime (). getTime ())); cal.add (Calendar.MINUTE, expiryTimeInMinutes); retourneer nieuwe datum (cal.getTime (). getTime ()); } // standaard constructeurs, getters en setters}

Merk op nullable = false op de Gebruiker om de gegevensintegriteit en -consistentie in het VerificationToken <->Gebruiker vereniging.

2.2. Voeg het ingeschakeld Veld naar Gebruiker

Aanvankelijk, toen het Gebruiker is geregistreerd, dit ingeschakeld veld wordt ingesteld op false. Tijdens het accountverificatieproces - indien succesvol - zal het worden waar.

Laten we beginnen met het toevoegen van het veld aan onze Gebruiker entiteit:

openbare klasse Gebruiker {... @Column (naam = "enabled") private boolean ingeschakeld; openbare gebruiker () {super (); this.enabled = false; } ...}

Merk op hoe we ook de standaardwaarde van dit veld instellen op false.

3. Tijdens accountregistratie

Laten we twee extra bedrijfslogica toevoegen aan de use case voor gebruikersregistratie:

  1. Genereer het VerificationToken voor de gebruiker en zet het vol
  2. Stuur het e-mailbericht voor accountbevestiging - inclusief een bevestigingslink met de VerificationToken's waarde

3.1. Een lentegebeurtenis gebruiken om het token te maken en de verificatie-e-mail te verzenden

Deze twee extra stukjes logica mogen niet rechtstreeks door de controller worden uitgevoerd, omdat het "onderpand" back-end-taken zijn.

De controller zal een Spring publiceren ApplicationEvent om de uitvoering van deze taken te activeren. Dit is net zo eenvoudig als het injecteren van de ApplicationEventPublisher en het vervolgens gebruiken om de voltooiing van de registratie te publiceren.

Voorbeeld 3.1. toont deze eenvoudige logica:

Voorbeeld 3.1.

@Autowired ApplicationEventPublisher eventPublisher @PostMapping ("/ user / registration") openbaar ModelAndView registerUserAccount (@ModelAttribute ("user") @Valid UserDto userDto, HttpServletRequest-verzoek, foutenfouten) {try {User registered = userService.registerNewUserAccount (userDervice); String appUrl = request.getContextPath (); eventPublisher.publishEvent (nieuwe OnRegistrationCompleteEvent (geregistreerd, request.getLocale (), appUrl)); } catch (UserAlreadyExistException uaeEx) {ModelAndView mav = nieuw ModelAndView ("registratie", "gebruiker", userDto); mav.addObject ("message", "Er bestaat al een account voor die gebruikersnaam / e-mail."); terugkeer mav; } catch (RuntimeException ex) {retourneer nieuw ModelAndView ("emailError", "user", userDto); } retourneer nieuw ModelAndView ("successRegister", "user", userDto); }

Een ander ding om op te merken is de proberen te vangen blok rond de publicatie van het evenement. Dit stuk code zal een foutpagina weergeven wanneer er een uitzondering is in de logica die wordt uitgevoerd na het publiceren van de gebeurtenis, in dit geval het verzenden van de e-mail.

3.2. De gebeurtenis en de luisteraar

Laten we nu kijken naar de daadwerkelijke implementatie van dit nieuwe OnRegistrationCompleteEvent die onze controller verzendt, evenals de luisteraar die het gaat afhandelen:

Voorbeeld 3.2.1. - De OnRegistrationCompleteEvent

openbare klasse OnRegistrationCompleteEvent breidt ApplicationEvent {private String appUrl; privé Locale locale; particuliere gebruiker gebruiker; openbare OnRegistrationCompleteEvent (gebruiker gebruiker, locale locale, string appUrl) {super (gebruiker); this.user = gebruiker; this.locale = locale; this.appUrl = appUrl; } // standaard getters en setters}

Voorbeeld 3.2.2. De RegistrationListener Verwerkt de OnRegistrationCompleteEvent

@Component openbare klasse RegistrationListener implementeert ApplicationListener {@Autowired private IUserService-service; @Autowired privé MessageSource-berichten; @Autowired privé JavaMailSender mailSender; @Override public void onApplicationEvent (OnRegistrationCompleteEvent-gebeurtenis) {this.confirmRegistration (evenement); } private void confirmRegistration (OnRegistrationCompleteEvent-gebeurtenis) {User user = event.getUser (); String-token = UUID.randomUUID (). ToString (); service.createVerificationToken (gebruiker, token); String recipientAddress = user.getEmail (); String subject = "Registratiebevestiging"; String confirmUrl = event.getAppUrl () + "/regitrationConfirm.html?token=" + token; String message = messages.getMessage ("message.regSucc", null, event.getLocale ()); SimpleMailMessage email = nieuwe SimpleMailMessage (); email.setTo (ontvangeradres); email.setSubject (onderwerp); email.setText (bericht + "\ r \ n" + "// localhost: 8080" + confirmatieUrl); mailSender.send (e-mail); }}

Hier de Bevestig registratie methode ontvangt de OnRegistrationCompleteEvent, extraheer al het nodige Gebruiker informatie ervan, maakt u het verificatietoken, houdt het vast en verzendt het vervolgens als een parameter in de "Bevestig registratie" koppeling.

Zoals hierboven vermeld, elk javax.mail.AuthenticationFailedException gegooid door JavaMailSender wordt afgehandeld door de controller.

3.3. Verwerking van de verificatietokenparameter

Wanneer de gebruiker de "Bevestig registratie”Link moeten ze erop klikken.

Zodra ze dat doen, haalt de controller de waarde van de tokenparameter uit het resulterende GET-verzoek en gebruikt deze om de Gebruiker.

Laten we dit proces eens bekijken in voorbeeld 3.3.1 .:

Voorbeeld 3.3.1. - RegistratieController Verwerking van de registratiebevestiging

@Autowired privé IUserService-service; @GetMapping ("/ regitrationConfirm") public String confirmRegistration (WebRequest-verzoek, Modelmodel, @RequestParam ("token") String-token) {Locale locale = request.getLocale (); VerificationToken verificatieToken = service.getVerificationToken (token); if (verificatieToken == null) {String message = messages.getMessage ("auth.message.invalidToken", null, locale); model.addAttribute ("bericht", bericht); retourneer "redirect: /badUser.html? lang =" + locale.getLanguage (); } Gebruiker gebruiker = verificatieToken.getUser (); Calendar cal = Calendar.getInstance (); if ((verificatieToken.getExpiryDate (). getTime () - cal.getTime (). getTime ()) <= 0) {String messageValue = messages.getMessage ("auth.message.expired", null, locale) model.addAttribute ("bericht", messageValue); retourneer "redirect: /badUser.html? lang =" + locale.getLanguage (); } user.setEnabled (true); service.saveRegisteredUser (gebruiker); retourneer "redirect: /login.html? lang =" + request.getLocale (). getLanguage (); }

De gebruiker wordt doorgestuurd naar een foutpagina met het bijbehorende bericht als:

  1. De VerificationToken bestaat niet, om de een of andere reden of
  2. De VerificationToken is verlopen

Zie voorbeeld 3.3.2. om de foutpagina te zien.

Voorbeeld 3.3.2. - De badUser.html

Zoals we kunnen zien, nu MyUserDetailsService maakt geen gebruik van de ingeschakeld vlag van de gebruiker - en dus staat het alleen de gebruiker toe om te authenticeren.

Nu zullen we een AuthenticationFailureHandler om de uitzonderingsboodschappen die vandaan komen aan te passen MyUserDetailsService. Onze CustomAuthenticationFailureHandler wordt getoond in Voorbeeld 4.2.:

Voorbeeld 4.2. - CustomAuthenticationFailureHandler:

@Component openbare klasse CustomAuthenticationFailureHandler breidt SimpleUrlAuthenticationFailureHandler {@Autowired private MessageSource-berichten uit; @Autowired privé LocaleResolver localeResolver; @Override public void onAuthenticationFailure (HttpServletRequest-verzoek, HttpServletResponse-antwoord, AuthenticationException-uitzondering) genereert IOException, ServletException {setDefaultFailureUrl ("/ login.html? Error = true"); super.onAuthenticationFailure (verzoek, antwoord, uitzondering); Locale locale = localeResolver.resolveLocale (verzoek); String errorMessage = messages.getMessage ("message.badCredentials", null, locale); if (exception.getMessage (). equalsIgnoreCase ("Gebruiker is uitgeschakeld")) {errorMessage = messages.getMessage ("auth.message.disabled", null, locale); } else if (exception.getMessage (). equalsIgnoreCase ("Gebruikersaccount is verlopen")) {errorMessage = messages.getMessage ("auth.message.expired", null, locale); } request.getSession (). setAttribute (WebAttributes.AUTHENTICATION_EXCEPTION, errorMessage); }}

We zullen moeten aanpassen login.html om de foutmeldingen weer te geven.

Voorbeeld 4.3. - Foutmeldingen weergeven op login.html:

 fout 

5. De persistentielaag aanpassen

Laten we nu de daadwerkelijke implementatie geven van enkele van deze bewerkingen met betrekking tot het verificatietoken en de gebruikers.

We behandelen:

  1. Een nieuw VerificationTokenRepository
  2. Nieuwe methoden in het IUserInterface en de implementatie ervan voor nieuwe CRUD-operaties die nodig zijn

Voorbeelden 5.1 - 5.3. toon de nieuwe interfaces en implementatie:

Voorbeeld 5.1. - De VerificationTokenRepository

openbare interface VerificationTokenRepository breidt JpaRepository {VerificationToken findByToken (String-token) uit; VerificationToken findByUser (gebruiker gebruiker); }

Voorbeeld 5.2. - De IUserService Koppel

openbare interface IUserService {User registerNewUserAccount (UserDto userDto) gooit UserAlreadyExistException; Gebruiker getUser (String verificatieToken); void saveRegisteredUser (gebruiker gebruiker); void createVerificationToken (gebruiker gebruiker, tekenreeks token); VerificationToken getVerificationToken (String VerificationToken); }

Voorbeeld 5.3. De Gebruikersservice

@Service @Transactional public class UserService implementeert IUserService {@Autowired private UserRepository repository; @Autowired privé VerificationTokenRepository tokenRepository; @Override public User registerNewUserAccount (UserDto userDto) gooit UserAlreadyExistException {if (emailExist (userDto.getEmail ())) {throw nieuwe UserAlreadyExistException ("Er is een account met dat e-mailadres:" + userDto.getEmail ()); } Gebruiker gebruiker = nieuwe gebruiker (); user.setFirstName (userDto.getFirstName ()); user.setLastName (userDto.getLastName ()); user.setPassword (userDto.getPassword ()); user.setEmail (userDto.getEmail ()); user.setRole (nieuwe rol (Integer.valueOf (1), gebruiker)); return repository.save (gebruiker); } private boolean emailExist (String e-mail) {return userRepository.findByEmail (e-mail)! = null; } @Override openbare gebruiker getUser (String verificatietoken) {User user = tokenRepository.findByToken (verificatietoken) .getUser (); terugkeer gebruiker; } @Override public VerificationToken getVerificationToken (String VerificationToken) {return tokenRepository.findByToken (VerificationToken); } @Override public void saveRegisteredUser (gebruiker gebruiker) {repository.save (gebruiker); } @Override public void createVerificationToken (gebruiker gebruiker, string token) {VerificationToken myToken = nieuw VerificationToken (token, gebruiker); tokenRepository.save (myToken); }}

6. Conclusie

In dit artikel hebben we het registratieproces uitgebreid met een op e-mail gebaseerde accountactiveringsprocedure.

De accountactiveringslogica vereist het verzenden van een verificatietoken naar de gebruiker via e-mail, zodat deze deze kan terugsturen naar de controller om zijn identiteit te verifiëren.

De implementatie van deze zelfstudie Registratie met Spring Security is te vinden in het GitHub-project - dit is een op Eclipse gebaseerd project, dus het moet gemakkelijk te importeren en uit te voeren zijn zoals het is.

De volgende » Spring Security Registration - Verzend de verificatie-e-mail opnieuw « Vorige Het registratieproces met Spring Security