In this article, we will learn to authenticate the users by sending OTP(One Time Password) from our spring boot web application. Here, I will explain to send the OTP to the person who tries to sign in or sign up in our application.
We will build complete application from scratch to finish, along with the front end or UI part.
Final Application will look like as shown below.
Note: Video tutorial is available in the bottom section of this article.
Mostly One Time Password Concept has implemented in Banking System or the application where security is a major concern. To achieve this feature I am going to use Google’s Guava library.
There are two place to send the OTP for Authentication.
- To Email Address.
- On Mobile Device as an SMS.
In this article, I am using the first approach, which sends the OTP to the Email Address. Here I am using Google’s Guava library to cache the OTP number for the validation and I also set the timer to the cached OTP expiry.
Follow the below mentioned steps to build the application.
Step 1: Create a Project from Spring Initializr
- Go to the Spring Initializr.
- Enter a Group name, com.pixeltrice.
- Mention the Artifact Id, spring-boot-OTP-enabled-app
- Add the following dependencies,
- Spring Web.
- Spring Data JPA.
- Spring Security.
- MySQL Driver.
- Thymeleaf.
- Java Mail Sender.
Step 2: Click on the Generate button, the project will be downloaded on your local system.
Step 3: Unzip and extract the project.
Step 4: Import the project in your IDE such as Eclipse.
Select File -> Import -> Existing Maven Projects -> Browse -> Select the folder spring-boot-OTP-enabled-app-> Finish.
Step 5: Configure the application.properties file.
#Properties for thymleaf template
spring.thymeleaf.cache=false
spring.thymeleaf.enabled=true
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
spring.application.name=Spring Boot OTP Enabled App
#Properties to connect with MySQL Database
spring.datasource.url=jdbc:mysql://localhost:3306/spring-boot-otp-app
spring.datasource.username=root
spring.datasource.password=root
#Disable the Http Authentication
#security.user.name=test
#security.user.password=test
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQLDialect
spring.jpa.properties.hibernate.id.new_generator_mappings = false
spring.jpa.properties.hibernate.format_sql = true
#smtp mail properties
spring.mail.host=smtp.gmail.com
spring.mail.port=587
spring.mail.username=your email address through which you will send the OTP
spring.mail.password= password of your email address
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true
Note: Make sure that Mysql username and password are correct.
Step 6: Configure the Spring Boot Main Class
As you can see we have added the dependency for Spring Security, so any HTTP Request which comes has to go through the Authentication process that means all our API and web pages are secure.
If anybody wants to access our API, then they must have to provide a valid username and password, as mentioned in the application.properties file.
#Enable the Http Authentication
security.user.name=test
security.user.password=test
In order to disable the basic Spring Boot Authentication, as we are doing above, we need to follow two points.
- Comment or remove the Spring security properties in the application.properties file. (Use “#” symbol to comment)
#security.user.name=test
#security.user.password=test
- Use
EnableAutoConfiguration annotation with
exclude attribute in the Spring Boot Main class as shown below.
@EnableAutoConfiguration(exclude = {SecurityAutoConfiguration.class})
If you tagged the Spring Boot main class with the above-mentioned annotation and removed the spring security properties from the application.properties file then it will exclude or remove the Spring security authentication from the application.
Since we are using the database in our application, so in order to run all database related queries we need to enable the JpaRepositories in our main class. So, to achieve this, tag the Spring Boot Main class with @EnableJpaRepositories
Hence, after doing all the above configuration, our main class will look as shown below.
package com.pixeltrice.springbootOTPenabledapp;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
@SpringBootApplication
@EnableAutoConfiguration(exclude = {SecurityAutoConfiguration.class})
@EnableJpaRepositories
public class SpringBootOtpEnabledAppApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootOtpEnabledAppApplication.class, args);
}
}
Step 7: Configure the class SpringSecurityConfig.java
Create a class with any name, and do the configuration as shown below.
package com.pixeltrice.springbootOTPenabledapp;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.access.AccessDeniedHandler;
@Configuration
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private AccessDeniedHandler accessDeniedHandler;
@Autowired
private UsersService usersService;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests()
.antMatchers("/","/aboutus").permitAll() //dashboard , Aboutus page will be permit to all user
.antMatchers("/admin/**").hasAnyRole("ADMIN") //Only admin user can login
.antMatchers("/user/**").hasAnyRole("USER") //Only normal user can login
.anyRequest().authenticated() //Rest of all request need authentication
.and()
.formLogin()
.loginPage("/login") //Loginform all can access ..
.defaultSuccessUrl("/dashboard")
.failureUrl("/login?error")
.permitAll()
.and()
.logout()
.permitAll()
.and()
.exceptionHandling().accessDeniedHandler(accessDeniedHandler());
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
auth.userDetailsService(usersService).passwordEncoder(passwordEncoder);;
}
@Bean
public AccessDeniedHandler accessDeniedHandler(){
return new CustomAccessDeniedHandler();
}
}
Explanation of each line of Code.
@Configuration: This annotation indicates that class having one or more methods which tagged with @Bean annotation, which means the return type of the method is Object or Bean.
Hence during the Runtime, Spring Container will process the Configuration class to generate the Bean or Object in their container.
@EnableWebSecurity: Since we have disabled the default Spring Security configuration in Step 6, here we are configuring our own spring security feature.
So to achieve this custom configuration we need to tagged the Class with @EnableWebSecurity annotation along with the @Configuration annotation.
And at the same time, we need to extend the WebSecurityConfigurerAdapter class to override the configure method.
configure() method Explanation
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests()
.antMatchers("/","/aboutus").permitAll()
.antMatchers("/admin/**").hasAnyRole("ADMIN")
.antMatchers("/user/**").hasAnyRole("USER")
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/dashboard")
.failureUrl("/login?error")
.permitAll()
.and()
.logout()
.permitAll()
.and()
.exceptionHandling()
.accessDeniedHandler(accessDeniedHandler());
}
- antMatchers(“/”,”/aboutus”).permitAll()
This line is giving permission to access about us page without any authentication. So, Anyone can access this page URL /aboutus.
2. .antMatchers(“/admin/**”).hasAnyRole(“ADMIN”)
The URL which starts with /admin can only be accessed by the person who has an Admin Role. For example localhost:8080/admin or localhost:8080/admin/profile.
3. .antMatchers(“/user/**”).hasAnyRole(“USER”)
URL which starts with /user can only be accessible to the person having Users Role.
4. .anyRequest().authenticated()
If any request comes for any other URL, need to be authenticated except the /aboutus page.
5. .and().formLogin().loginPage(“/login”)
Login page with URL /login can be accesable to anyone.
6. defaultSuccessUrl(“/dashboard”)
Once the login is done, then by default dashboard page will come with the URL /dashboard.
7. .failureUrl(“/login?error”)
If login is not success, then error URL get triggered, or called.
8. .permitAll()
Once authentication and login success, then user can acess all the URL.
9. .exceptionHandling().accessDeniedHandler(accessDeniedHandler())
If logged in users try to access other URLs, for which he or she is not allowed then Access Denied will occur, so to handle this scenario, we already Autowired or injected the predefined class named AccessDeniedHandler.
configureGlobal() method explanation
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
auth.userDetailsService(usersService).passwordEncoder(passwordEncoder);;
}
Here we are injecting an Object type of AuthenticationManagerBuilder, which is used to authenticate the users’ credentials from the database. If you noticed in the above code we are passing a usersService object, still, we did not create a class yet.
But once we create the class we will attach all users’ details in that object, and from the above method, it will be validated.
We are also using the password encoder to store or convert the password in BCrypt Format.
accessDeniedHandler() Explanation
@Bean
public AccessDeniedHandler accessDeniedHandler(){
return new CustomAccessDeniedHandler();
}
Here we defined our own custom class to Handle the Access Denied scenario. In the next step, we will create a class for CustomAccessDeniedHandler.
Step 8: Create a CustomAccessDeniedHandler.java
package com.pixeltrice.springbootOTPenabledapp;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.access.AccessDeniedHandler;
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(
HttpServletRequest request,
HttpServletResponse response,
AccessDeniedException exc) throws IOException, ServletException {
Authentication auth
= SecurityContextHolder.getContext().getAuthentication();
response.sendRedirect(request.getContextPath() + "/accessDenied");
}
}
Step 9: Add the Google guava dependency in the pom.xml file
Since I told you in an earlier section of this article that we will use google guava library to cache the OTP. So will implement in the Step 10 code.
<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>23.0</version>
</dependency>
Step 10: Create a OTPService.java class
In this class we will the OTP Expiry time to 4 minutes.
package com.pixeltrice.springbootOTPenabledapp;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import org.springframework.stereotype.Service;
import com.google.common.cache.LoadingCache;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
@Service
public class OTPService {
private static final Integer EXPIRE_MINS = 4;
private LoadingCache<String, Integer> otpCache;
public OTPService(){
super();
otpCache = CacheBuilder.newBuilder().
expireAfterWrite(EXPIRE_MINS, TimeUnit.MINUTES)
.build(new CacheLoader<String, Integer>() {
public Integer load(String key) {
return 0;
}
});
}
public int generateOTP(String key){
Random random = new Random();
int otp = 100000 + random.nextInt(900000);
otpCache.put(key, otp);
return otp;
}
public int getOtp(String key){
try{
return otpCache.get(key);
}catch (Exception e){
return 0;
}
}
public void clearOTP(String key){
otpCache.invalidate(key);
}
}
Explanation of OTPService.java class
- private static final Integer EXPIRE_MINS = 4
We are assigning the Expiry time of OTP to 4 minutes.
2. private LoadingCache<String, Integer> otpCache
With the use of LoadingCache, the values are automatically loaded as a cache which stored on your local system. Caches stored in the form of key-value pairs.
In the further line of code, we storing the caches as a username is key and OTP as a value with the following line of code.
otpCache = CacheBuilder.newBuilder().
expireAfterWrite(EXPIRE_MINS, TimeUnit.MINUTES)
.build(new CacheLoader<String, Integer>() {
public Integer load(String key) {
return 0;
}
The above code will store the expiry time of OTP as well, and we already declared the EXPIRE_MINS = 4, which means after 4 minutes the stored OTP will not be valid.
3. Generate the Random OTP number.
public int generateOTP(String key){
Random random = new Random();
int otp = 100000 + random.nextInt(900000);
otpCache.put(key, otp);
return otp;
}
The purpose of the above method is to generate and return the OTP values. In the above line of code, we are using a Random() method to generate the Random numbers and storing in the otp variable.
otpCache.put(key, otp): Finally we are saving or storing the OTP and username, as web browser cache in our local machine.
4. Fetching the OTP from the cache file.
public int getOtp(String key){
try{
return otpCache.get(key);
}catch (Exception e){
return 0;
}
}
The above line of code is used to fetching the stored OTP from your local system corresponding to the mentioned key that is the username. If the value is not present then it will return 0.
Note: You can see your cache files on the following path for windows C:\Users\Username\AppData\Local\Google\Chrome\User Data\Default\Cache
5. Clear or Remove the OTP from the cache.
public void clearOTP(String key){
otpCache.invalidate(key);
}
The above method will delete or remove the stored OTP from the cache for the corresponding username as we pass in key.
Step 11: Create EmailService.java class
In this class, we will define a method to send the OTP to the logged-in user’s email address.
package com.pixeltrice.springbootOTPenabledapp;
import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Service;
@Service
public class EmailService {
@Autowired
private JavaMailSender javaMailSender;
public void sendOtpMessage(String to, String subject, String message) throws MessagingException {
MimeMessage msg = javaMailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(msg, true);
helper.setTo(to);
helper.setSubject(subject);
helper.setText(message, true);
javaMailSender.send(msg);
}
}
In the above code, we have autowired or injected the predefined class named JavaMailSender which contains the method to send the mail.
javaMailSender.send(simpleMailMessage);
The above method is send the mail to the logged in user’s email address.
Step 12: Create a Controller class named OTPController.java
In this class we will define an API to generate the OTP and send it to the logged-in user, and also will create an API to validate the OTP.
package com.pixeltrice.springbootOTPenabledapp;
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class OTPController {
@Autowired
public OTPService otpService;
@Autowired
public EmailService emailService;
@GetMapping("/generateOtp")
public String generateOTP(){
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String username = auth.getName();
int otp = otpService.generateOTP(username);
//Generate The Template to send OTP
EmailTemplate template = new EmailTemplate("SendOtp.html");
Map<String,String> replacements = new HashMap<String,String>();
replacements.put("user", username);
replacements.put("otpnum", String.valueOf(otp));
String message = template.getTemplate(replacements);
emailService.sendOtpMessage("Logged in Users EmailAddres", "OTP -SpringBoot", message);
return "otppage";
}
@RequestMapping(value ="/validateOtp", method = RequestMethod.GET)
public @ResponseBody String validateOtp(@RequestParam("otpnum") int otpnum){
final String SUCCESS = "Entered Otp is valid";
final String FAIL = "Entered Otp is NOT valid. Please Retry!";
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String username = auth.getName();
//Validate the Otp
if(otpnum >= 0){
int serverOtp = otpService.getOtp(username);
if(serverOtp > 0){
if(otpnum == serverOtp){
otpService.clearOTP(username);
return (SUCCESS);
}
else {
return FAIL;
}
}else {
return FAIL;
}
}else {
return FAIL;
}
}
}
Explanation
- API to genrate the OTP.
@GetMapping("/generateOTP")
public String generateOTP(){
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String username = auth.getName();
int otp = otpService.generateOTP(username);
//Generate The Template to send OTP
EmailTemplate template = new EmailTemplate("SendOtp.html");
Map<String,String> replacements = new HashMap<String,String>();
replacements.put("user", username);
replacements.put("otpnum", String.valueOf(otp));
String message = template.getTemplate(replacements);
emailService.sendOtpMessage("Logged in Users EmailAddres", "OTP -SpringBoot", message);
return "otppage";
}
In the above, we have written a code to generate the OTP and at the same time, OTP will attach to a template in order to deliver the user’s email address.
Note: We will create a Email Template class and SendOtp.html in next step.
2. To validate the OTP
The method validateOtp() is used to validate the OTP received from the UI side as a parameter.
@RequestMapping(value ="/validateOtp", method = RequestMethod.GET)
public @ResponseBody String validateOtp(@RequestParam("otpnum") int otpnum){
final String SUCCESS = "Entered Otp is valid";
final String FAIL = "Entered Otp is NOT valid. Please Retry!";
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String username = auth.getName();
//Validate the Otp
if(otpnum >= 0){
int serverOtp = otpService.getOtp(username);
if(serverOtp > 0){
if(otpnum == serverOtp){
otpService.clearOTP(username);
return ("Entered Otp is valid");
}
else {
return SUCCESS;
}
}else {
return FAIL;
}
}else {
return FAIL;
}
}
Explanation of each line of code.
- Getting the username of current logged in user.
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String username = auth.getName();
The above line of code will return the username of the current logged in user.
2. Fetch the OTP which is stored as cache in the local system.
int serverOtp = otpService.getOtp(username);
As we already define a getOtp() in an OTPService.java in Step 9 to fetch the OTP stored in the logged-in user’s cache based on username, so it will return the OTP, and assigned to the variable serverOtp.
3. Compare the OTP value with the number we recieved as an parameter.
In this section, we are comparing the value received as request parameter with the stored cache value of OTP.
if(serverOtp > 0){
if(otpnum == serverOtp){
otpService.clearOTP(username);
return ("Entered Otp is valid");
}
else {
return SUCCESS;
}
}else {
return FAIL;
}
If everything is worked fine then it will return the successful message otherwise the failure message.
Step 13: Create a SendOtp.html
Make sure you should create the SendOtp.html on under the source folder. spring-boot-OTP-enabled-app\src\main\java\SendOtp.html
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<h1> Hi {{user}}</h1>
<br/>
<h2> Your Otp Number is {{otpnum}}</h2>
<br/>
Thanks,
</body>
</html>
So whenever mail will delivered, it will goes with above format.
Have you noticed in the above figure the message received is in bold format, that is only because of h1 tagged used in SendOtp.html, Don’t worry just keep on reading, I explained everything.
Step 14: Create a EmailTemplate.java
This class we will create a template so that OTP will be attached to the template before delivering to the user’s email address.
package com.pixeltrice.springbootOTPenabledapp;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.Map;
public class EmailTemplate {
private String template;
private Map<String, String> replacementParams;
public EmailTemplate(String customtemplate) {
try {
this.template = loadTemplate(customtemplate);
} catch (Exception e) {
this.template = "Empty";
}
}
private String loadTemplate(String customtemplate) throws Exception {
ClassLoader classLoader = getClass().getClassLoader();
File file = new File(classLoader.getResource(customtemplate).getFile());
String content = "Empty";
try {
content = new String(Files.readAllBytes(file.toPath()));
} catch (IOException e) {
throw new Exception("Could not read template = " + customtemplate);
}
return content;
}
public String getTemplate(Map<String, String> replacements) {
String cTemplate = this.template;
//Replace the String
for (Map.Entry<String, String> entry : replacements.entrySet()) {
cTemplate = cTemplate.replace("{{" + entry.getKey() + "}}", entry.getValue());
}
return cTemplate;
}
}
Step 15: Create a Controller class named HomeController.java
In this class we will define all basic API such as dashboard, login, logout, etc.
package com.pixeltrice.springbootOTPenabledapp;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class HomeController {
@Value("${spring.application.name}")
String appName;
@Autowired
public OTPService otpService;
@GetMapping("/")
public String homePage(Model model) {
String message = " Welcome to Home Page";
model.addAttribute("appName", appName);
model.addAttribute("message", message);
return "signin";
}
@GetMapping("/dashboard")
public String dashboard(){
return "dashboard";
}
@GetMapping("/login")
public String login() {
return "signin";
}
@GetMapping("/admin")
public String admin() {
return "admin";
}
@GetMapping("/user")
public String user() {
return "user";
}
@GetMapping("/aboutus")
public String about() {
return "aboutus";
}
@GetMapping("/403")
public String error403() {
return "error/403";
}
@RequestMapping(value="/logout", method = RequestMethod.GET)
public @ResponseBody String logout(HttpServletRequest request, HttpServletResponse response){
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null){
String username = auth.getName();
//Remove the recently used OTP from server.
otpService.clearOTP(username);
new SecurityContextLogoutHandler().logout(request, response, auth);
}
return "redirect:/login?logout";
}
}
Explanation of the above code.
- Method homePage()
@GetMapping("/")
public String homePage(Model model) {
String message = " Welcome to Home Page";
model.addAttribute("appName", appName);
model.addAttribute("message", message);
return "signin";
}
In this method, we are setting a model attributes with application name and message. We already defined the application name property in the application.properties file as shown below.
spring.application.name=Spring Boot OTP Enabled App
When the API “/” or localhost:8080 called then the method homePage() will get executed and it will redirect to the signin page. Since we already enabled the thymeleaf template in our application with the use of the following property in the application.properties file.
spring.thymeleaf.enabled=true
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
And along with that we also configured the prefix and suffix. So, whenever any method returns the String value, the prefix and suffix will automatically add to that. For example in the above homePage() method, it is returning the signin, but actually it returning the localhost:8080/signin.html.
Note: Once the user will redirect to the page signin.html, then along with the application name, the message will also be displayed. We will design the HTML part in the last section of this article.
2. Method dashboard()
@GetMapping("/dashboard")
public String dashboard(){
return "dashboard";
}
Whenever the /dashboard API gets called, the dashboard() will be triggered or executed. It will return the localhost:8080/dashboard.html.
Note: All the remaining API is working exactly the same as above two, but I want to explain the last API that is /logout.
3. Method logout()
@RequestMapping(value="/logout", method = RequestMethod.GET)
public @ResponseBody String logout(HttpServletRequest request, HttpServletResponse response){
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null){
String username = auth.getName();
//Remove the recently used OTP from server.
otpService.clearOTP(username);
new SecurityContextLogoutHandler().logout(request, response, auth);
}
return "redirect:/login?logout";
}
When anybody call the /logout then logout() method will get executed.
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
The purpose of the above line of code is to return the details of current logged in user. Once you get the object of Authentication, then you can easily get the username, ROLES, and many more of the current logged in person. For example auth.getName() gives the username.
String username = auth.getName();
It will return the username of the current logged in user.
otpService.clearOTP(username);
We will define the clearOTP() method in OTPService.java class still we did not create yet, but the purpose of the clearOTP() method is to remove the recently used OTP from the server.
new SecurityContextLogoutHandler().logout(request, response, auth);
The above line of code used to completely logged out the user from the application.
return "redirect:/login?logout";
The return type of the method logout() is localhost:8080/redirect:/login?logout.html.
Step 16: Create a Model or Pojo Class for User.
This class is an Entity class for the users’ table which we are going to create in further steps.
package com.pixeltrice.springbootOTPenabledapp;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name = "users")
public class UserPojo {
@Id
@Column(name="username")
private String username;
@Column(name="password")
private String password;
@Column(name="role")
private String role;
@Column(name="full_name")
private String fullName;
@Column(name="country")
private String country;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
public String getFullName() {
return fullName;
}
public void setFullName(String fullName) {
this.fullName = fullName;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
}
Step 17: Create a interface which extend the JPA Repository class.
Since we are using the Hibernate as JPA for communicating with the database. So the interface will look like as shown below.
package com.pixeltrice.springbootOTPenabledapp;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface UserRepository extends JpaRepository<UserPojo, String>{
UserPojo findByUsername(String username);
}
In the above code, we have extended a predefined class JpaRepository. It contains all the methods to perform the CRUD operation, we don’t have to write our own method.
JpaRepository<UserPojo, Integer>: In the angular bracket <> we have to mention the entity class name and the data type of the primary key. Since in our case, the Entity class name is UserPojo and the primary key is userId having of Integer type.
@Repository: This annotation indicates that the class or interface is completely dedicated to performing all sorts of CRUD Operations such as Create, update, read, or delete the data from the database.
Step 18: Create a Class UsersService.java to validate the logged-in user on the database level.
In this step, we will create UsersService class which extends the predefined UsersDetailService class to override the method in order to validate the user’s credentials on database level.
package com.pixeltrice.springbootOTPenabledapp;
import java.util.Arrays;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
@Service
public class UsersService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserPojo userPojo = userRepository.findByUsername(username);
GrantedAuthority authority = new SimpleGrantedAuthority(userPojo.getRole());
UserDetails userDetails = (UserDetails) new User(userPojo.getUsername(),
userPojo.getPassword(), Arrays.asList(authority));
return userDetails;
}
}
Step 19: Go to Mysql workbench and create a new schema and under that create a table with name users.
SQL Query to create a users table.
CREATE TABLE IF NOT EXISTS `users` (
`username` varchar(255) NOT NULL,
`country` varchar(255) DEFAULT NULL,
`full_name` varchar(255) DEFAULT NULL,
`password` varchar(255) DEFAULT NULL,
`role` varchar(255) DEFAULT NULL,
PRIMARY KEY (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
The above query will create a users table in MySql Workbench.
SQL Query to insert some users.
INSERT INTO `users` (`username`, `country`, `full_name`, `password`, `role`) VALUES
('admin', 'India', 'Admin -Jack', '$2a$10$Z5F2Elzpnwx6kp0CYLmdo.Tcv8SZWMANvlr/PWr6.IxWWXnAi7KNC', 'ROLE_ADMIN'),
('user', 'USA', 'User-Ruby', '$2a$10$KFsPE4H9buyijUht5nQU..qdpkDRA5q6zPeACtLVWAaDh3kMzPxXG', 'ROLE_USER');
In the above code, you can see we are storing the password in the form of BCrypt format. It is highly recommended to not store the password in plain text format because of security issues.
Note: When you login as Admin then enter the following credentials.
username : admin
password : admin // plain text of stored password in the database.
For User’s loggin , the credentials are:
username : user
password: user // plain text of stored password in the database
Now all the coding parts have been completed, from the next step we need to work on building the HTML pages or templates, please follow the below-mentioned link in order to get the HTML files.
Step 20: Once you added the HTML files, our folder structure in Eclipse or any other IDE will look like as shown below.
Since in the HTML files I have uses the bootstrap library for CSS, and jquery for javascript, please add the following dependencies in the pom.xml file.
<!-- Optional, for bootstrap -->
<dependency>
<groupId>org.webjars</groupId>
<artifactId>bootstrap</artifactId>
<version>3.3.7</version>
</dependency>
<!-- Optional, for jquery -->
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>2.2.4</version>
</dependency>
Hence our final pom.xml file will look like as shown below.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.pixeltrice</groupId>
<artifactId>spring-boot-OTP-enabled-app</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-boot-OTP-enabled-app</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>23.0</version>
</dependency>
<!-- Optional, for bootstrap -->
<dependency>
<groupId>org.webjars</groupId>
<artifactId>bootstrap</artifactId>
<version>3.3.7</version>
</dependency>
<!-- Optional, for jquery -->
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>2.2.4</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Step 21: Run the Application and perform the testing to get the different results.
Please follow the steps after running the application as shown in below figure.
Note: If you are getting some error after clicking OTP, Then might happen either of any mentioned reason below.
- Less Secure app is not enabled on your Gmail account(Turn it On).
- Two-step verification might be turned on. (please do off it for testing).
Please if make sure everything is same as mentioned in above points.
Congratulations! You successfully send an OTP and done authentication from Spring Boot Application. You will get completed Source code from the below link.
Summary
In this article, we have learned to send the OTP to the person who wants to log in to our spring boot application. If you face any difficulties or problems, please feel free to ask me anytime. You can also comment down below or send me an email for any doubts or feedback.
You might also like this article.
- Make a Voice Call from Spring Boot Application to Mobile Phone
- Send an SMS or Message from Spring Boot Application to Mobile Phone
- How to send email using Spring Boot Application
- Spring Boot Security using OAuth2 with JWT
- Build Spring Boot Restful CRUD API with Hibernate and Postgresql from scratch
- Deploy the Spring Boot Application on External Tomcat Server