preface

Forgetting your password and resetting your password via email is a common business requirement. I also need to use this service in the process of developing my personal small project. Today, I will give you a practical business practice.

The development environment

  • Springboot: 1.5.16) RELEASE

The business process

According to the controller function is divided into two parts:

  1. User applies to reset email:
  • The user enters a mailbox on the page
  • The server checks whether resets are allowed (whether the user the mailbox points to exists, whether resets are too frequent, whether resets have reached the daily request limit)
  • After the verification is successful, the validate table is written into the application record, including the token, user email address, and user ID
  • Send mail (including links with tokens)
  • The user clicks on the in-mail link
  • Jump to the new password input page
  • Submit password reset request (POST contains token, new password)
  1. User reset password
  • The server validates the token (whether the token has expired, whether the user has initiated other tokens)
  • Find the user ID from the VALIDATE table and change the user password

In actual combat

  1. Pom.xml adds email dependencies
<! - mail: email--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-mail</artifactId> </dependency>Copy the code
  1. Add the PM_VALIDATE table structure

Reset_token is generated by UUID, type is resetPassword by default, and user_id is the user ID of the user table

-- ----------------------------
-- Table structure for pm_validate
-- ----------------------------
DROP TABLE IF EXISTS `pm_validate`;
CREATE TABLE `pm_validate` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) NOT NULL,
  `email` varchar(40) NOT NULL,
  `reset_token` varchar(40) NOT NULL,
  `type` varchar(20) NOT NULL,
  `gmt_create` datetime DEFAULT NULL,
  `gmt_modified` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Copy the code

Generate or write corresponding POJOs and Mapper. Since I used mybatis -Generator plug-in, I need to run the plug-in to generate the corresponding POJO and Mapper.

  1. Modify application.properties to add mailbox configuration
Send mail configuration
spring.mail.host=smtp.gmail.com
[email protected]
spring.mail.password=xxxxxxx
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true
spring.mail.properties.mail.smtp.starttls.required=true
Copy the code
  1. Write controllers and services
  • ValidateController
@RestController
@RequestMapping(value = "/validate")
public class ValidateController {

    @Autowired
    private ValidateService validateService;

    @Autowired
    private UserService userService;

    @Value("${spring.mail.username}") private String from; /** * The request for forgetting the password should be sent no more than five times a day and the interval for each request should be no less than one minute * @param Email * @param request * @return
     */
    @ApiOperation(value = "Send forgotten password email", notes = "Send forgotten password email")
    @RequestMapping(value = "/sendValidationEmail", method = {RequestMethod.POST})
    public ResponseData<String> sendValidationEmail(@ApiParam("Email address") @RequestParam("email") String email,
                                               HttpServletRequest request){
        ResponseData<String> responseData = new ResponseData<>();
        List<User> users = userService.findUserByEmail(email);
        if (users == null){
            responseData.jsonFill(2, "The user of this mailbox does not exist", null);
        }else {
            if(validateService sendValidateLimitation (email, 5, 1)) {/ / if allowed to reset the password, then in pm_validate insert a row in the table, Validate = new Validate(); validateService.insertNewResetRecord(validate, users.get(0), UUID.randomUUID().toString()); // Set email content String appUrl = request.getScheme() +": / /" + request.getServerName();
                SimpleMailMessage passwordResetEmail = new SimpleMailMessage();
                passwordResetEmail.setFrom(from);
                passwordResetEmail.setTo(email);
                passwordResetEmail.setSubject("[E-commerce price Monitoring] Forget your password");
                passwordResetEmail.setText("You are requesting to reset your password, please click this link to reset your password: \n" + appUrl + "/validate/reset? token=" + validate.getResetToken());
                validateService.sendPasswordResetEmail(passwordResetEmail);
                responseData.jsonFill(1, null, null);
            }else {
                responseData.jsonFill(2,"Operation too frequent, please try again later!",null); }}returnresponseData; } /** ** Match url token to database token, if successful, can change the password, token validity period is 60 minutes * @param token * @param password * @param confirmPassword * @return
     */
    @ApiOperation(value = "Reset password", notes = "Reset password")
    @RequestMapping(value = "/resetPassword", method = RequestMethod.POST)
    public ResponseData<String> resetPassword(@ApiParam("token") @RequestParam("token") String token,
                                              @ApiParam("Password") @RequestParam("password") String password,
                                              @ApiParam("Password confirmation") @RequestParam("confirmPassword") String confirmPassword){ ResponseData<String> responseData = new ResponseData<>(); / / find the validate record through token List < validate > validates. = validateService findUserByResetToken (token);if (validates == null){
            responseData.jsonFill(2,"The reset request does not exist",null);
        }else {
            Validate validate = validates.get(0);
            if (validateService.validateLimitation(validate.getEmail(), Long.MAX_VALUE, 60, token)){
                Integer userId = validate.getUserId();
                if (password.equals(confirmPassword)) {
                    userService.updatePassword(password, userId);
                    responseData.jsonFill(1, null,null);
                }else {
                    responseData.jsonFill(2,"Confirm password is inconsistent with password, please re-enter", null); }}else {
                responseData.jsonFill(2,"This link is invalid",null); }}returnresponseData; }}Copy the code
  • ValidateService
public interface ValidateService {
    void sendPasswordResetEmail(SimpleMailMessage email);
    int insertNewResetRecord(Validate validate, User users, String token);
    List<Validate> findUserByResetToken(String resetToken);
    boolean validateLimitation(String email, long requestPerDay, long interval, String token);
    boolean sendValidateLimitation(String email, long requestPerDay, long interval);
}
Copy the code
  • ValidateServiceImpl
@Service public class ValidateServiceImpl implements ValidateService { @Autowired private JavaMailSender javaMailSender;  @Autowired private ValidateMapper validateMapper; /** * Send email: @override @async public void sendPasswordResetEmail(SimpleMailMessage email){@override @async public void sendPasswordResetEmail(SimpleMailMessage email){ javaMailSender.send(email); } /** * insert a validate entry into the pM_user table with the email attribute from the userID. Tokens are generated by UUID * @param validate * @param users * @param token * @return
     */
    @Override
    public int insertNewResetRecord(Validate validate, User users, String token){
        validate.setUserId(users.getId());
        validate.setEmail(users.getEmail());
        validate.setResetToken(token);
        validate.setType("passwordReset");
        validate.setGmtCreate(new Date());
        validate.setGmtModified(new Date());
        returnvalidateMapper.insert(validate); } /** * resets the request record * @param token * @ in the pM_VALIDATE tablereturn
     */
    @Override
    public List<Validate> findUserByResetToken(String token){
        ValidateExample validateExample = new ValidateExample();
        ValidateExample.Criteria criteria = validateExample.createCriteria();
        criteria.andResetTokenEqualTo(token);
        returnvalidateMapper.selectByExample(validateExample); } /** * Verify whether to send a reset email: The maximum number of requests for a new password per email is requestPerDay, and the interval between the last request and the last one is interval minutes. * @param email * @param requestPerDay * @param interval * @return*/ @Override public boolean sendValidateLimitation(String email, long requestPerDay, long interval){ ValidateExample validateExample = new ValidateExample(); ValidateExample.Criteria criteria= validateExample.createCriteria(); criteria.andEmailEqualTo(email); List<Validate> validates = validateMapper.selectByExample(validateExample); // If no record is found, it means that the first application is directly releasedif (validates.isEmpty()) {
            return true; } // There are records, Long countTodayValidation = validates.stream().filter(x-> dateutils.issameDay (x.gottgmtModified ()), new Date())).count(); Optional validate = validates.stream().map(Validate::getGmtModified).max(Date::compareTo); Date dateOfLastRequest = new Date();if (validate.isPresent()) dateOfLastRequest = (Date) validate.get();
        long intervalForLastRequest = new Date().getTime() - dateOfLastRequest.getTime();

        returncountTodayValidation <= requestPerDay && intervalForLastRequest >= interval * 60 * 1000; } /** * Verify that the connection is invalid: the connection is invalid in two ways: 1. Timeout 2. The last requested link automatically overwrites the previous link (see code) * @param email * @param requestPerDay * @param interval * @return
     */
    @Override
    public boolean validateLimitation(String email, long requestPerDay, long interval, String token){
        ValidateExample validateExample = new ValidateExample();
        ValidateExample.Criteria criteria= validateExample.createCriteria();
        criteria.andEmailEqualTo(email);
        List<Validate> validates = validateMapper.selectByExample(validateExample);
        // 有记录才会调用该函数,只需判断是否超时
        Optional validate = validates.stream().map(Validate::getGmtModified).max(Date::compareTo);
        Date dateOfLastRequest = new Date();
        if (validate.isPresent()) dateOfLastRequest = (Date) validate.get();
        long intervalForLastRequest = new Date().getTime() - dateOfLastRequest.getTime();

        Optional lastRequestToken = validates.stream().filter(x-> x.getResetToken().equals(token)).map(Validate::getGmtModified).findAny();
        Date dateOfLastRequestToken = new Date();
        if (lastRequestToken.isPresent()) {
            dateOfLastRequestToken = (Date) lastRequestToken.get();
        }
        returnintervalForLastRequest <= interval * 60 * 1000 && dateOfLastRequest == dateOfLastRequestToken; }}Copy the code

conclusion

The above realization of the whole reset password process, front-end web design to achieve.

Pay attention to my

I am man three dao dao, currently for the background development engineer. Focus on backend development, network security, Python crawler and other technologies.

Come and chat with me on wechat: yangzd1102

Github:github.com/qqxx6661

Original blog main content

  • Written interview review knowledge handbook
  • Leetcode algorithm
  • Sword point offer algorithm analysis
  • Python crawler related technical analysis and practice
  • Background development related technical analysis and actual combat

Update the following blogs

1. Csdn

blog.csdn.net/qqxx6661

Have columns: Leetcode problem solving (Java/Python), Python crawler development, interview assist manual

2. Zhihu

www.zhihu.com/people/yang…

Have column: code farmer interview assist manual

3. The nuggets

Juejin. Cn/user / 211951…

4. Jane books

www.jianshu.com/u/b5f225ca2…

Personal public account: Rude3Knife

If this article is helpful to you, save it and send it to your friends

Personal project: e-commerce price monitoring website

I long-term maintenance of personal projects, completely free, please support.

Realize the function

  • Jingdong commodity monitoring: Set the commodity ID and expected price, and automatically send an email to remind users when the commodity price is [lower] than the set expected price. (within 1 hour)
  • Jingdong Commodity Category Monitoring: After users subscribe to a specific category, [self-operated commodities] with a discount of more than 30% will be selected and sent to users via email to remind them.
  • Category commodity browsing, commodity history price curve, commodity history highest and lowest price
  • Continuously updated…

Web site address

pricemonitor.online/