Registration/Login System using Spring Boot and Spring Security

hussaincode

Mohammad Hussain

Posted on February 28, 2022

Registration/Login System using Spring Boot and Spring Security

Registration/Login System using Spring Boot and Spring Security

Hussain Code

·Feb 20, 2022·

7 min read

Subscribe to my newsletter and never missmy upcoming articles

Subscribe

In this article we are going to build complete Registration/Login System using Spring Boot and Spring Security.

Technology used :-

 => Spring Boot

   => Spring Security

   => Java Mail

   => Email verification with expiry
Enter fullscreen mode Exit fullscreen mode

1. Create Project using Spring Initializer

Follow this link and you will be redirected to the spring initializer with all the dependencies requires for this project.

Download this project and you will get the zip file. unzip it and open with intelliJ Idea.

step1.PNG

2. Create appuser package

Inside this package we will configure all the user details in different classes and Interfaces.

  • Create a AppUser class and define all the properties of the user like firstName, lastName, email, password, etc.
  • Create a AppUserRepository interface and an inherit the JpaRepository class to do all the database operations on the users.
@Transactional(readOnly = true)
public interface AppUserRepository extends JpaRepository<AppUser, Long> {
    Optional<AppUser> findByEmail(String email);

    @Transactional
    @Modifying
    @Query("UPDATE AppUser a " +
            "SET a.enabled = TRUE WHERE a.email = ?1")
    int enableAppUser(String email);
}
Enter fullscreen mode Exit fullscreen mode
  • Create a AppUserRole enum to define the role of the user.

public enum AppUserRole {
USER,
ADMIN
}

  • Create a AppUserService class to define the service in terms of how user will register and share the data to login.
@Service
@AllArgsConstructorpublic class AppUserService implements UserDetailsService {private static final String USER_NOT_FOUND ="user with email %s not found!";private final AppUserRepository appUserRepository;private final BCryptPasswordEncoder bCryptPasswordEncoder;private final ConfirmationTokenService confirmationTokenService;

    @Overridepublic UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {return appUserRepository.findByEmail(email)
                .orElseThrow(() ->new UsernameNotFoundException(String.format(USER_NOT_FOUND,email)));
    }public String signUpUser(AppUser appUser){
       boolean userExists = appUserRepository
                .findByEmail(appUser.getEmail())
                .isPresent();if (userExists){// TODO: CHECK OF ATTRIBUTES ARE THE SAME AND//TODO: IF EMAIL NOT CONFIRMED SENDD CONFIRMATION MAILthrownew IllegalStateException("email already taken!");
       }
       String encodedPassword = bCryptPasswordEncoder.encode(appUser.getPassword());
       appUser.setPassword(encodedPassword);

       appUserRepository.save(appUser);

        String token = UUID.randomUUID().toString();//TODO: Send confirmation token ConfirmationToken confirmationToken =new ConfirmationToken(
                token,
                LocalDateTime.now(),
                LocalDateTime.now().plusMinutes(15),
                appUser
        );
        confirmationTokenService.saveConfirmationToken(confirmationToken);//TODO Send Emailreturn token;
    }publicint enableAppUser(String email) {return appUserRepository.enableAppUser(email);
    }
}
Enter fullscreen mode Exit fullscreen mode

3. Create registration package

Inside this package we will configure all the registration details in different classes and Interfaces that will register a user we have in appuser package.

  • Create a UserRegistration Class to map a registration endpoint
@RequestMapping(path = "api/v1/registration")
@AllArgsConstructor
public class UserRegistration {

    private final RegistrationService registrationService;

    @PostMapping
    public String register(@RequestBody RegistrationRequest request){

        return registrationService.register(request);
    }

    @GetMapping(path = "confirm")
    public String confirm(@RequestParam("token") String token) {
        return registrationService.confirmToken(token);
    }

}
Enter fullscreen mode Exit fullscreen mode
  • Create a RegistrationRequest class to request all the data from the user.
@AllArgsConstructor
@EqualsAndHashCode
@ToString
public class RegistrationRequest {
    private final String firstName;
    private final String lastName;
    private final String email;
    private final String password;
}
Enter fullscreen mode Exit fullscreen mode
  • Create a EmailValidator class that will validate the user's email
@Service@AllArgsConstructorpublicclassEmailValidatorimplementsPredicate<String> {@Overridepublicbooleantest(String s){returnfalse;
    }
}
Enter fullscreen mode Exit fullscreen mode
  • Create a RegistrationService class to map the relation between the user and registration endpoint.
@AllArgsConstructor
public class RegistrationService {
    private final AppUserService appUserService;
    private final EmailValidator emailValidator;
    private final EmailSender emailSender;
    private final ConfirmationTokenService confirmationTokenService;

    public String register(RegistrationRequest request) {

        boolean isValidEmail = emailValidator.test(request.getEmail());

        if(isValidEmail){
            throw new IllegalStateException("Email is not valid!");
        }
            String token = appUserService.signUpUser(
                    new AppUser(
                            request.getFirstName(),
                            request.getLastName(),
                            request.getEmail(),
                            request.getPassword(),
                            AppUserRole.USER
                    )
            );
        String link = "http://localhost:8080/api/v1/registration/confirm/?token=" + token;
        emailSender.send(
                request.getEmail(),
                buildEmail(request.getFirstName(), link) );

        return  token;
    }
    @Transactional
    public String confirmToken(String token) {
        ConfirmationToken confirmationToken = confirmationTokenService
                .getToken(token)
                .orElseThrow(() ->
                        new IllegalStateException("token not found"));

        if (confirmationToken.getConfirmedAt() != null) {
            throw new IllegalStateException("email already confirmed");
        }

        LocalDateTime expiredAt = confirmationToken.getExpiredAt();

        if (expiredAt.isBefore(LocalDateTime.now())) {
            throw new IllegalStateException("token expired");
        }

        confirmationTokenService.setConfirmedAt(token);
        appUserService.enableAppUser(
                confirmationToken.getAppUser().getEmail());
        return "confirmed";
    }

    private String buildEmail(String name, String link) {
        return "<div style=\"font-family:Helvetica,Arial,sans-serif;font-size:16px;margin:0;color:#0b0c0c\">\n" +
                "\n" +
                "<span style=\"display:none;font-size:1px;color:#fff;max-height:0\"></span>\n" +
                "\n" +
                "  <table role=\"presentation\" width=\"100%\" style=\"border-collapse:collapse;min-width:100%;width:100%!important\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\">\n" +
                "    <tbody><tr>\n" +
                "      <td width=\"100%\" height=\"53\" bgcolor=\"#0b0c0c\">\n" +
                "        \n" +
                "        <table role=\"presentation\" width=\"100%\" style=\"border-collapse:collapse;max-width:580px\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" align=\"center\">\n" +
                "          <tbody><tr>\n" +
                "            <td width=\"70\" bgcolor=\"#0b0c0c\" valign=\"middle\">\n" +
                "                <table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" style=\"border-collapse:collapse\">\n" +
                "                  <tbody><tr>\n" +
                "                    <td style=\"padding-left:10px\">\n" +
                "                  \n" +
                "                    </td>\n" +
                "                    <td style=\"font-size:28px;line-height:1.315789474;Margin-top:4px;padding-left:10px\">\n" +
                "                      <span style=\"font-family:Helvetica,Arial,sans-serif;font-weight:700;color:#ffffff;text-decoration:none;vertical-align:top;display:inline-block\">Confirm your email</span>\n" +
                "                    </td>\n" +
                "                  </tr>\n" +
                "                </tbody></table>\n" +
                "              </a>\n" +
                "            </td>\n" +
                "          </tr>\n" +
                "        </tbody></table>\n" +
                "        \n" +
                "      </td>\n" +
                "    </tr>\n" +
                "  </tbody></table>\n" +
                "  <table role=\"presentation\" class=\"m_-6186904992287805515content\" align=\"center\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" style=\"border-collapse:collapse;max-width:580px;width:100%!important\" width=\"100%\">\n" +
                "    <tbody><tr>\n" +
                "      <td width=\"10\" height=\"10\" valign=\"middle\"></td>\n" +
                "      <td>\n" +
                "        \n" +
                "                <table role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" style=\"border-collapse:collapse\">\n" +
                "                  <tbody><tr>\n" +
                "                    <td bgcolor=\"#1D70B8\" width=\"100%\" height=\"10\"></td>\n" +
                "                  </tr>\n" +
                "                </tbody></table>\n" +
                "        \n" +
                "      </td>\n" +
                "      <td width=\"10\" valign=\"middle\" height=\"10\"></td>\n" +
                "    </tr>\n" +
                "  </tbody></table>\n" +
                "\n" +
                "\n" +
                "\n" +
                "  <table role=\"presentation\" class=\"m_-6186904992287805515content\" align=\"center\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" style=\"border-collapse:collapse;max-width:580px;width:100%!important\" width=\"100%\">\n" +
                "    <tbody><tr>\n" +
                "      <td height=\"30\"><br></td>\n" +
                "    </tr>\n" +
                "    <tr>\n" +
                "      <td width=\"10\" valign=\"middle\"><br></td>\n" +
                "      <td style=\"font-family:Helvetica,Arial,sans-serif;font-size:19px;line-height:1.315789474;max-width:560px\">\n" +
                "        \n" +
                "            <p style=\"Margin:0 0 20px 0;font-size:19px;line-height:25px;color:#0b0c0c\">Hi " + name + ",</p><p style=\"Margin:0 0 20px 0;font-size:19px;line-height:25px;color:#0b0c0c\"> Thank you for registering. Please click on the below link to activate your account: </p><blockquote style=\"Margin:0 0 20px 0;border-left:10px solid #b1b4b6;padding:15px 0 0.1px 15px;font-size:19px;line-height:25px\"><p style=\"Margin:0 0 20px 0;font-size:19px;line-height:25px;color:#0b0c0c\"> <a href=\"" + link + "\">Activate Now</a> </p></blockquote>\n Link will expire in 15 minutes. <p>See you soon</p>" +
                "        \n" +
                "      </td>\n" +
                "      <td width=\"10\" valign=\"middle\"><br></td>\n" +
                "    </tr>\n" +
                "    <tr>\n" +
                "      <td height=\"30\"><br></td>\n" +
                "    </tr>\n" +
                "  </tbody></table><div class=\"yj6qo\"></div><div class=\"adL\">\n" +
                "\n" +
                "</div></div>";
    }
}
Enter fullscreen mode Exit fullscreen mode
  • Now create one more package inside the registration package in which we will configure the token from the user for the successful registration.
  • And then inside this token package, we will create a ConfirmationToken class to create the token for the user.
@Setter
@NoArgsConstructor
@Entity
public class ConfirmationToken {
    @SequenceGenerator(
            name = "confirmation_token_sequence",
            sequenceName = "confirmation_token_sequence",
            allocationSize = 1
    )
    @Id
    @GeneratedValue(
            strategy = GenerationType.SEQUENCE,
            generator = "confirmation_token_sequence"
    )
    private Long id;
    @Column(nullable = false)
    private  String token;
    @Column(nullable = false)
    private LocalDateTime createdAt;
    @Column(nullable = false)
    private LocalDateTime expiredAt;
    private LocalDateTime confirmedAt;

    @ManyToOne
    @JoinColumn(
            nullable = false,
            name= "app_user_id"
    )
    private AppUser appUser;

    public ConfirmationToken(String token,
                             LocalDateTime createdAt,
                             LocalDateTime expiredAt,
                             AppUser appUser) {
        this.token = token;
        this.createdAt = createdAt;
        this.expiredAt = expiredAt;
        this.appUser =appUser;
    }
   }
Enter fullscreen mode Exit fullscreen mode
  • Run the project and then you can go to postman and hit this url - localhost:8080/api/v1/registration, you will get the access token

postmanRegistration.PNG

  • Create an interface ConfirmationTokenRepository and extends the JpaRepository to do all the database operation like to save the token to specific user in databse.
interface ConfirmationTokenRepository extends
        JpaRepository<ConfirmationToken,Long> {

    Optional<ConfirmationToken> findByToken(String token);

    @Transactional
    @Modifying
    @Query("UPDATE ConfirmationToken c " +
            "SET c.confirmedAt = ?2 " +
            "WHERE c.token = ?1")
    int updateConfirmedAt(String token,
                          LocalDateTime confirmedAt);


}
Enter fullscreen mode Exit fullscreen mode
  • Create a ConfirmationTokenService class to save this token to the specific users for 15 mins so that he can activate their account within 15 min and will be able to login.
  • we have set the token expiry time to 15 mins for the security purpose, we can increase or decrease the expiration time of token according to our purpose.
@AllArgsConstructor
public class ConfirmationTokenService {

    private final ConfirmationTokenRepository confirmationTokenRepository;

    public void saveConfirmationToken(ConfirmationToken token){
        confirmationTokenRepository.save(token);
    }

    public Optional<ConfirmationToken> getToken(String token) {
        return confirmationTokenRepository.findByToken(token);
    }

    public int setConfirmedAt(String token) {
        return confirmationTokenRepository.updateConfirmedAt(
                token, LocalDateTime.now());
    }
}
Enter fullscreen mode Exit fullscreen mode
  • Now you can hit the access token url localhost:8080/api/v1/registration/confirm/?token=17de5cc5-2f43-48e3-acd2-c47f26bba311
  • And access token would be the same which we have created while hitting the registration url

postmanTokenConfirmation.PNG

4. Create an email package in which we will configure the property of email sending.

  • Create an interface EmailSender, where we will define the send method that will take t2 String parameters "to" and "email"

\`public interface EmailSender {
void send(String to, String email);
}

`

  • Create a classEmailServicewherewewillimplementthissendmethodandthepropertytosendanemailtoaparticularuser'semailwhowilltrytoregister.

`@Service@AllArgsConstructorpublicclassEmailServiceimplementsEmailSender{privatefinalstatic Logger LOGGER = LoggerFactory.getLogger(EmailService.class);privatefinal JavaMailSender javaMailSender;

@Override
@Asyncpublicvoid send(String to, String email) {try {
        MimeMessage mimeMessage = javaMailSender.createMimeMessage();
        MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage,"utf-8");
        mimeMessageHelper.setText(email,true);
        mimeMessageHelper.setTo(to);
        mimeMessageHelper.setSubject("Confirm your email");
        mimeMessageHelper.setFrom("info@hussaincode.in");
        javaMailSender.send(mimeMessage);
    }catch (MessagingException e){
        LOGGER.error("failed to send email",e);thrownew IllegalStateException("failed to send email");

    }
}
Enter fullscreen mode Exit fullscreen mode

}
`

5. For sending a mail we have used the MailDev Service.

MailDev is a free service which we can use to send an email to user for the confirmation of registration.

MailDDev.PNG

You can install the MailDev using below commands.

  1. To install MailDev - $ npm install -g maildev
  2. To run MailDev - $ maildev
  3. Once you have installed it you will get a url like to access the email service.

MailDev webapp running at http://0.0.0.0:1080

MailDev SMTP Server running at 0.0.0.0:1025

This is complete Registration/Login Project steps and code. If you have any doubt leave a comment and i will reply.

Get the complete code base of this project on my GitHub

Like

Share this

💖 💪 🙅 🚩
hussaincode
Mohammad Hussain

Posted on February 28, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related