Registration – Activate a New Account by Email – 注册 – 通过电子邮件激活一个新账户

最后修改: 2014年 12月 3日


1. Overview


This article continues the ongoing Registration with Spring Security series with one of the missing pieces of the registration process – verifying the user’s email to confirm their account.

本文继续正在进行的用Spring Security注册系列,介绍注册过程中缺少的一个环节–验证用户的电子邮件以确认其帐户

The registration confirmation mechanism forces the user to respond to a “Confirm Registration” email sent after successful registration to verify his email address and activate their account. The user does this by clicking a unique activation link sent to them over email.


Following this logic, a newly registered user will not be able to log into the system until this process is completed.


2. A Verification Token


We will make use of a simple verification token as the key artifact through which a user is verified.


2.1. The VerificationToken Entity

2.1.VerificationToken 实体

The VerificationToken entity must meet the following criteria:


  1. It must link back to the User (via a unidirectional relation)
  2. It will be created right after registration
  3. It will expire within 24 hours following its creation
  4. Has a unique, randomly generated value

Requirements 2 and 3 are part of the registration logic. The other two are implemented in a simple VerificationToken entity like the one in Example 2.1.:


Example 2.1.


public class VerificationToken {
    private static final int EXPIRATION = 60 * 24;

    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    private String token;
    @OneToOne(targetEntity = User.class, fetch = FetchType.EAGER)
    @JoinColumn(nullable = false, name = "user_id")
    private User user;
    private Date expiryDate;
    private Date calculateExpiryDate(int expiryTimeInMinutes) {
        Calendar cal = Calendar.getInstance();
        cal.setTime(new Timestamp(cal.getTime().getTime()));
        cal.add(Calendar.MINUTE, expiryTimeInMinutes);
        return new Date(cal.getTime().getTime());
    // standard constructors, getters and setters

Note the nullable = false on the User to ensure data integrity and consistency in the VerificationToken<->User association.

注意User上的nullable = false,以确保VerificationToken<User关联中的数据完整性和一致性。

2.2. Add the enabled Field to User


Initially, when the User is registered, this enabled field will be set to false. During the account verification process – if successful – it will become true.


Let us start by adding the field to our User entity:

让我们先把这个字段添加到我们的 User实体中。

public class User {
    @Column(name = "enabled")
    private boolean enabled;
    public User() {

Note how we also set the default value of this field to false.


3. During Account Registration


Let’s add two additional pieces of business logic to the user registration use case:


  1. Generate the VerificationToken for the User and persist it
  2. Send out the email message for account confirmation – which includes a confirmation link with the VerificationToken’s value

3.1. Using a Spring Event to Create the Token and Send the Verification Email


These two additional pieces of logic should not be performed by the controller directly because they are “collateral” back-end tasks.

这两个额外的逻辑不应该由控制器直接执行,因为它们是 “附带的 “后端任务。

The controller will publish a Spring ApplicationEvent to trigger the execution of these tasks. This is as simple as injecting the ApplicationEventPublisher and then using it to publish the registration completion.

控制器将发布一个Spring ApplicationEvent来触发这些任务的执行。这就像注入ApplicationEventPublisher一样简单,然后用它来发布注册完成。

Example 3.1. shows this simple logic:


Example 3.1.


ApplicationEventPublisher eventPublisher

public ModelAndView registerUserAccount(
  @ModelAttribute("user") @Valid UserDto userDto, 
  HttpServletRequest request, Errors errors) { 
    try {
        User registered = userService.registerNewUserAccount(userDto);
        String appUrl = request.getContextPath();
        eventPublisher.publishEvent(new OnRegistrationCompleteEvent(registered, 
          request.getLocale(), appUrl));
    } catch (UserAlreadyExistException uaeEx) {
        ModelAndView mav = new ModelAndView("registration", "user", userDto);
        mav.addObject("message", "An account for that username/email already exists.");
        return mav;
    } catch (RuntimeException ex) {
        return new ModelAndView("emailError", "user", userDto);

    return new ModelAndView("successRegister", "user", userDto);

One additional thing to notice is the try catch block surrounding the publishing of the event. This piece of code will display an error page whenever there is an exception in the logic executed after the publishing of the event, which in this case is the sending of the email.

需要注意的另一件事是围绕事件发布的try catch块。这段代码将显示一个错误页面,只要在事件发布后执行的逻辑中出现异常,在本例中就是发送电子邮件的过程。

3.2. The Event and the Listener


Let’s now see the actual implementation of this new OnRegistrationCompleteEvent that our controller is sending out, as well as the listener that is going to handle it:


Example 3.2.1. – The OnRegistrationCompleteEvent


public class OnRegistrationCompleteEvent extends ApplicationEvent {
    private String appUrl;
    private Locale locale;
    private User user;

    public OnRegistrationCompleteEvent(
      User user, Locale locale, String appUrl) {
        this.user = user;
        this.locale = locale;
        this.appUrl = appUrl;
    // standard getters and setters

Example 3.2.2. The RegistrationListener Handles the OnRegistrationCompleteEvent

示例 3.2.2.RegistrationListener处理OnRegistrationCompleteEvent

public class RegistrationListener implements 
  ApplicationListener<OnRegistrationCompleteEvent> {
    private IUserService service;
    private MessageSource messages;
    private JavaMailSender mailSender;

    public void onApplicationEvent(OnRegistrationCompleteEvent event) {

    private void confirmRegistration(OnRegistrationCompleteEvent event) {
        User user = event.getUser();
        String token = UUID.randomUUID().toString();
        service.createVerificationToken(user, token);
        String recipientAddress = user.getEmail();
        String subject = "Registration Confirmation";
        String confirmationUrl 
          = event.getAppUrl() + "/regitrationConfirm?token=" + token;
        String message = messages.getMessage("message.regSucc", null, event.getLocale());
        SimpleMailMessage email = new SimpleMailMessage();
        email.setText(message + "\r\n" + "http://localhost:8080" + confirmationUrl);

Here, the confirmRegistration method will receive the OnRegistrationCompleteEvent, extract all the necessary User information from it, create the verification token, persist it, and then send it as a parameter in the “Confirm Registration” link.


As was mentioned above, any javax.mail.AuthenticationFailedException thrown by JavaMailSender will be handled by the controller.


3.3. Processing the Verification Token Parameter


When the user receives the “Confirm Registration” link they should click on it.


Once they do – the controller will extract the value of the token parameter in the resulting GET request and will use it to enable the User.


Let’s see this process in Example 3.3.1.:


Example 3.3.1. – RegistrationController Processing the Registration Confirmation

示例3.3.1.- RegistrationController处理注册确认

private IUserService service;

public String confirmRegistration
  (WebRequest request, Model model, @RequestParam("token") String token) {
    Locale locale = request.getLocale();
    VerificationToken verificationToken = service.getVerificationToken(token);
    if (verificationToken == null) {
        String message = messages.getMessage("auth.message.invalidToken", null, locale);
        model.addAttribute("message", message);
        return "redirect:/badUser.html?lang=" + locale.getLanguage();
    User user = verificationToken.getUser();
    Calendar cal = Calendar.getInstance();
    if ((verificationToken.getExpiryDate().getTime() - cal.getTime().getTime()) <= 0) {
        String messageValue = messages.getMessage("auth.message.expired", null, locale)
        model.addAttribute("message", messageValue);
        return "redirect:/badUser.html?lang=" + locale.getLanguage();
    return "redirect:/login.html?lang=" + request.getLocale().getLanguage(); 

The user will be redirected to an error page with the corresponding message if:


  1. The VerificationToken does not exist, for some reason or
  2. The VerificationToken has expired

See Example 3.3.2. to see the error page.


Example 3.3.2. – The badUser.html

示例3.3.2。- badUser.html

    <h1 th:text="${param.message[0]}>Error Message</h1>
    <a th:href="@{/registration.html}" 

If no errors are found, the user is enabled.


There are two opportunities for improvement in handling the VerificationToken checking and expiration scenarios:

在处理 VerificationToken检查和过期情况时,有两个改进的机会。

  1. We can use a Cron Job to check for token expiration in the background
  2. We can give the user the opportunity to get a new token once it has expired

We’ll defer the generation of a new token for a future article and assume that the user does indeed successfully verify their token here.


4. Adding Account Activation Checking to the Login Process


We need to add the code that will check if the user is enabled:


Let’s see this in Example 4.1. which shows the loadUserByUsername method of MyUserDetailsService.


Example 4.1.


UserRepository userRepository;

public UserDetails loadUserByUsername(String email) 
  throws UsernameNotFoundException {
    boolean enabled = true;
    boolean accountNonExpired = true;
    boolean credentialsNonExpired = true;
    boolean accountNonLocked = true;
    try {
        User user = userRepository.findByEmail(email);
        if (user == null) {
            throw new UsernameNotFoundException(
              "No user found with username: " + email);
        return new
    } catch (Exception e) {
        throw new RuntimeException(e);

As we can see, now MyUserDetailsService not uses the enabled flag of the user – and so it will only allow enabled the user to authenticate.


Now, we will add an AuthenticationFailureHandler to customize the exception messages coming from MyUserDetailsService. Our CustomAuthenticationFailureHandler is shown in Example 4.2.:


Example 4.2. – CustomAuthenticationFailureHandler:

示例 4.2. – CustomAuthenticationFailureHandler:

public class CustomAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {

    private MessageSource messages;

    private LocaleResolver localeResolver;

    public void onAuthenticationFailure(HttpServletRequest request, 
      HttpServletResponse response, AuthenticationException exception)
      throws IOException, ServletException {

        super.onAuthenticationFailure(request, response, exception);

        Locale locale = localeResolver.resolveLocale(request);

        String errorMessage = messages.getMessage("message.badCredentials", null, locale);

        if (exception.getMessage().equalsIgnoreCase("User is disabled")) {
            errorMessage = messages.getMessage("auth.message.disabled", null, locale);
        } else if (exception.getMessage().equalsIgnoreCase("User account has expired")) {
            errorMessage = messages.getMessage("auth.message.expired", null, locale);

        request.getSession().setAttribute(WebAttributes.AUTHENTICATION_EXCEPTION, errorMessage);

We will need to modify login.html to show the error messages.


Example 4.3. – Display error messages at login.html:

示例4.3。- 在login.html处显示错误信息:

<div th:if="${param.error != null}" 

5. Adapting the Persistence Layer


Let’s now provide the actual implementation of some of these operations involving the verification token as well as the users.


We’ll cover:


  1. A new VerificationTokenRepository
  2. New methods in the IUserInterface and its implementation for new CRUD operations needed

Examples 5.1 – 5.3. show the new interfaces and implementation:

例5.1 – 5.3.显示了新的接口和实现。

Example 5.1. – The VerificationTokenRepository

示例 5.1.VerificationTokenRepository

public interface VerificationTokenRepository 
  extends JpaRepository<VerificationToken, Long> {

    VerificationToken findByToken(String token);

    VerificationToken findByUser(User user);

Example 5.2. – The IUserService Interface

示例 5.2.IUserService 接口

public interface IUserService {
    User registerNewUserAccount(UserDto userDto) 
      throws UserAlreadyExistException;

    User getUser(String verificationToken);

    void saveRegisteredUser(User user);

    void createVerificationToken(User user, String token);

    VerificationToken getVerificationToken(String VerificationToken);

Example 5.3. The UserService

示例5.3. 用户服务

public class UserService implements IUserService {
    private UserRepository repository;

    private VerificationTokenRepository tokenRepository;

    public User registerNewUserAccount(UserDto userDto) 
      throws UserAlreadyExistException {
        if (emailExist(userDto.getEmail())) {
            throw new UserAlreadyExistException(
              "There is an account with that email adress: " 
              + userDto.getEmail());
        User user = new User();
        user.setRole(new Role(Integer.valueOf(1), user));

    private boolean emailExist(String email) {
        return userRepository.findByEmail(email) != null;
    public User getUser(String verificationToken) {
        User user = tokenRepository.findByToken(verificationToken).getUser();
        return user;
    public VerificationToken getVerificationToken(String VerificationToken) {
        return tokenRepository.findByToken(VerificationToken);
    public void saveRegisteredUser(User user) {;
    public void createVerificationToken(User user, String token) {
        VerificationToken myToken = new VerificationToken(token, user);;

6. Conclusion


In this article, we’ve expanded the registration process to include an email based account activation procedure.


The account activation logic requires sending a verification token to the user via email so that they can send it back to the controller to verify their identity.


The implementation of this Registration with Spring Security tutorial can be found in the GitHub project – this is an Eclipse based project, so it should be easy to import and run as it is.

GitHub项目中可以找到该注册教程的实现 – 这是一个基于Eclipse的项目,因此应该很容易导入并按原样运行。

Next »

Spring Security Registration – Resend Verification Email

« Previous

The Registration Process With Spring Security