Last Updated on 01 December 2020   |   Print Email
In this Spring Security tutorial, I’d love to share with you guys how to implement password expiration function for an existing Spring Boot application based on standard technologies like Spring Data JPA, Spring Security, Thymeleaf and MySQL database.Suppose that you have an existing Spring Boot application in which authentication is already implemented, and now it needs to be updated for implementing password expiration functionality, with the following requirements:- Users must change password after 30 days since the last time they updated their passwords.- The application will require a user to change his password when it found that the password expires, during the time he’s using the website (including upon successful login).The user will be asked to change expired password in the following form:Now, let’s see how to write code in details.
1. Update Database table and Entity class
Suppose that the user information is stored in a table named customers, so you need to alter the table for adding a new column called password_changed_time, as shown below:
The type of the column is DATETIME, so it will store time that is precise to seconds. Your application should update value for this column somehow, e.g. upon user registration or activation.Then update the corresponding entity class as follows:
@Entity
@Table(name = "customers")
public class Customer {
private static final long PASSWORD_EXPIRATION_TIME
= 30L * 24L * 60L * 60L * 1000L; // 30 days
@Column(name = "password_changed_time")
private Date passwordChangedTime;
public boolean isPasswordExpired() {
if (this.passwordChangedTime == null) return false;
long currentTime = System.currentTimeMillis();
long lastChangedTime = this.passwordChangedTime.getTime();
return currentTime > lastChangedTime + PASSWORD_EXPIRATION_TIME;
}
// other fields, getters and setters are not shown
}
Here, we declare a constant of type long to represent the number of milliseconds in 30 days (password expiration time). The field passwordChangedTime maps to the corresponding column in the database table, and the isPasswordExpired() method is used to check whether a user’s password expires or not.
2. Update User Service Class
Next, update the user business class (CustomerServices in my case) to implement a method for updating password of a customer, as follows:
@Service
@Transactional
public class CustomerServices {
@Autowired CustomerRepository customerRepo;
@Autowired PasswordEncoder passwordEncoder;
public void changePassword(Customer customer, String newPassword) {
String encodedPassword = passwordEncoder.encode(newPassword);
customer.setPassword(encodedPassword);
customer.setPasswordChangedTime(new Date());
customerRepo.save(customer);
}
}
The changePassword() method will be used by a controller when a user changes his password. And as you notice, the password changed time value is set to the current datetime so the user will have next 30 days until the newly changed password expires.
3. Code Password Expiration Filter
Next, we need to code a filter class to intercept all requests coming the application, in order to check if the currently logged in user has expired password or not, as follows:
If the request is for getting static resources, the application will continue the filter chain – do not process anymore.And below is code of the method that returns a UserDetails object representing an authenticated user:
private Customer getLoggedInCustomer() {
Authentication authentication
= SecurityContextHolder.getContext().getAuthentication();
Object principal = null;
if (authentication != null) {
principal = authentication.getPrincipal();
}
if (principal != null && principal instanceof CustomerUserDetails) {
CustomerUserDetails userDetails = (CustomerUserDetails) principal;
return userDetails.getCustomer();
}
return null;
}
Here, CustomerDetails is a class that implements the UserDetails interface defined by Spring Security. You should have similar class in your project.And the last method in this filter class is for redirecting the user to the change password page if his password expires, as shown below:
So you can notice the relative URL of the change password page is /change_password – we’ll code a controller class that handles this URL in the next section.
4. Code Password Controller Class
Next, create a new Spring MVC controller class to show the change password page as well as processing password change, with some initial code as follows:
@Controller
public class PasswordController {
@Autowired
private CustomerServices customerService;
@Autowired
private PasswordEncoder passwordEncoder;
@GetMapping("/change_password")
public String showChangePasswordForm(Model model) {
model.addAttribute("pageTitle", "Change Expired Password");
return "change_password";
}
@PostMapping("/change_password")
public String processChangePassword() {
// implement later...
}
}
As you can see, the first handler method simply returns the logical view name change_password which will be resolved to a corresponding HTML page which is described next.
5. Code Change Password Page
Next, create the change_password.html page with the following code in the body of the document:
This will display the change password form with 3 password fields: old password, new password and confirm new password. Also put the following Javascript code to validate the matching of two new password fields:
function checkPasswordMatch(fieldConfirmPassword) {
if (fieldConfirmPassword.value != $("#newPassword").val()) {
fieldConfirmPassword.setCustomValidity("Passwords do not match!");
} else {
fieldConfirmPassword.setCustomValidity("");
}
}
You can also notice that I use HTML 5, Thymeleaf, Bootstrap and jQuery. And implement the second handler method for updating user’s password upon submission of the change password page, as follows:
@PostMapping("/change_password")
public String processChangePassword(HttpServletRequest request, HttpServletResponse response,
Model model, RedirectAttributes ra,
@AuthenticationPrincipal Authentication authentication) throws ServletException {
CustomerUserDetails userDetails = (CustomerUserDetails) authentication.getPrincipal();
Customer customer = userDetails.getCustomer();
String oldPassword = request.getParameter("oldPassword");
String newPassword = request.getParameter("newPassword");
model.addAttribute("pageTitle", "Change Expired Password");
if (oldPassword.equals(newPassword)) {
model.addAttribute("message", "Your new password must be different than the old one.");
return "change_password";
}
if (!passwordEncoder.matches(oldPassword, customer.getPassword())) {
model.addAttribute("message", "Your old password is incorrect.");
return "change_password";
} else {
customerService.changePassword(customer, newPassword);
request.logout();
ra.addFlashAttribute("message", "You have changed your password successfully. "
+ "Please login again.");
return "redirect:/login";
}
}
Here, it gets a UserDetailsobject that represent the authenticated user. Then it checks to make sure that the new password is different than the old one, and the old password is correct. If both conditions are met, it updates the user’s password with new one and logs the user out – then showing the login page.Next, we’re ready to test the password expiration feature.
6. Test Password Expiration Function
Use a database tool like MySQL Workbench to update changed password time of a user to a value which is older than 30 days from the current date. Then try to login with that user’s email, you should see the application asks for changing password as shown below:Try to enter different values for the 2 new password fields and click Change Password button, the browser should catch the error immediately:Next, try to enter wrong old password but same new passwords and click the button, you would see the following error:Now, submit the form with correct old password, and two same new passwords, you should see the following screen:Now enter the new password to login, and you should be able to log into the application. Also check the database to make sure that the value of password changed time column is set to a new value which is the current date and time.Congratulations, you have been successfully implementing the password expiration feature for an existing Spring Boot application. To see the coding in action, I recommend you to watch the following video:
Nam Ha Minh is certified Java programmer (SCJP and SCWCD). He began programming with Java back in the days of Java 1.4 and has been passionate about it ever since. You can connect with him on Facebook and watch his Java videos on YouTube.
Nice work! I love your work dude! BTW, when I tried to replicate, got some errors in Spring Boot 2.4.3. Solved them by checking this webpage. Actually don't understand WHY! Send you the link github.com/.../26117
Comments
spring.datasource.url=jdbc:mysql://localhost:3306/airline
spring.datasource.username=root
spring.datasource.password=
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
server.port=8083
github.com/.../26117