There is a recent need to adjust the Ruoyi management background: if the user has not changed his password for three months (a long period of time), he needs to forcibly change his password when logging in, otherwise he will not be able to log in to the system.
1. Back-end project adjustment
From the perspective of requirements, we need to add a field to the user table to mark the time when the user last changed their password.
1. Adjust table structure
1) Add the pwd_time field to the user table sys_user
Each time the password is updated, it needs to be updated to the current system time.
2) Adjust the corresponding xml mapping file SysUserMapper.xml
3) Adjust the corresponding entity object SysUser
Also add the corresponding get/set methods.
2. Adjust the login interface
The reason why we need to adjust the login interface is because the subsequent reset password interface requires a token after login to be called successfully, otherwise it will prompt that the login has expired.
1)SysLoginService
Added to determine whether the user has not changed the password for more than three months
public boolean isPwdExpire(String username) { SysUser sysUser = userService.selectUserByUserName(username); Date pwdTime = sysUser.getPwdTime(); LocalDate pwdDate = pwdTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); // Get the current date LocalDate currentDate = LocalDate.now(); // Calculate date difference Period period = Period.between(pwdDate, currentDate); // Check if the month difference is greater than or equal to three months if (period.toTotalMonths() >= 3) { return true; } return false; }
2)SysLoginController
In the login method, add the above method to determine
boolean pwdExpire = loginService.isPwdExpire(loginBody.getUsername()); if (pwdExpire) { ajax.put("res_code", 1001); String signKey = Constants.RESET_SIGN_KEY + loginBody.getUsername(); String signCode = IdUtils.fastSimpleUUID(); redisCache.setCacheObject(signKey, signCode, Constants.RESET_EXPIRATION, TimeUnit.MINUTES); ajax.put("reset_sign",signCode); }
The return code res_code is added here, which is used by the page to determine whether the currently logged in user wants to reset the password.
At the same time, for security reasons, some verification mechanisms are also used to generate a sign mark and set it in the redis cache for verification when resetting the password.
3. Add password reset interface
Originally, there was a place to reset the password in the personal center of the backend management system, and there was also a corresponding interface to reset the password. However, this is done when the user has logged in and can enter the backend normally, which is different from our needs here. The same, so we need to write another interface to handle password reset during login.
1)SysProfileController
/** * reset Password */ @Log(title = "Personal Information", businessType = BusinessType.UPDATE) @PostMapping("/resetPwd") public AjaxResult resetPwd(@RequestBody ResetBody resetBody) { String username = resetBody.getUsername(); String sign = resetBody.getSign(); String signKey = Constants.RESET_SIGN_KEY + username; String cacheSign = redisCache.getCacheObject(signKey); if (StringUtils.isEmpty(cacheSign)) { return AjaxResult.error("The link has expired"); } if (!cacheSign.equals(sign)) { return AjaxResult.error("sign is wrong"); } String code = resetBody.getCode(); String uuid = resetBody.getUuid(); sysLoginService.validateCaptcha(username, code, uuid, false); SysUser sysUser = userService.selectUserByUserName(username); if (sysUser == null) { return AjaxResult.error("User does not exist"); } String oldPassword = resetBody.getOldPassword(); String password = sysUser.getPassword(); if (!SecurityUtils.matchesPassword(oldPassword, password)) { return AjaxResult.error("Failed to change password, old password is wrong"); } String newPassword = resetBody.getNewPassword(); if (userService.resetUserPwd(username, SecurityUtils.encryptPassword(newPassword)) > 0) { LoginUser loginUser = getLoginUser(); //Delete user cache records tokenService.delLoginUser(loginUser.getToken()); // Delete cache redisCache.deleteObject(signKey); // The front end redirects to the login page return AjaxResult.success(); } return AjaxResult.error("Exception in changing password, please contact the administrator"); }
The method handles some verifications, such as the sign mark mentioned above, and the graphic verification code on the page, etc., to increase security.
After the reset password is generated, the user cache record needs to be cleared so that the user must log in again.
2)ResetBody
Receives the request parameters for resetting the password.
public class ResetBody { /** * username */ private String username; private String oldPassword; private String newPassword; private String confirmPassword; private String code; private String sign; private String uuid; //getter/setter... }
2. Front-end project adjustment
1.Adjust src/store/modules/user.js
Here you need to return the information after calling the login interface and bring it back to the login page for subsequent operations.
2. Adjust src/api/system/user.js
Add an interface to reset password
// User password reset export function resetUserProfilePwd(data) { return request({ url: '/system/user/profile/resetPwd', method: 'post', data: data }) }
3. Add the reset password page src/views/reset.vue
<template> <div class="register"> <el-form ref="resetForm" :model="resetForm" :rules="resetRules" class="register-form"> <h3 class="title">Change password</h3> <el-form-item label="old password" prop="oldPassword"> <el-input v-model="resetForm.oldPassword" placeholder="Please enter the old password" type="password" show-password/> </el-form-item> <el-form-item label="New Password" prop="newPassword"> <el-input v-model="resetForm.newPassword" placeholder="12 digits composed of numbers, uppercase and lowercase letters, and symbols" type="password" show-password/> </el-form-item> <el-form-item label="Confirm Password" prop="confirmPassword"> <el-input v-model="resetForm.confirmPassword" placeholder="Please confirm password" type="password" show-password/> </el-form-item> <el-form-item prop="code" v-if="captchaOnOff"> <el-input v-model="resetForm.code" auto-complete="off" placeholder="Verification code" style="width: 63%" > <svg-icon slot="prefix" icon-class="validCode" class="el-input__icon input-icon" /> </el-input> <div class="register-code"> <img :src="codeUrl" @click="getCode" class="register-code-img"/> </div> </el-form-item> <el-form-item style="width:100%;"> <el-button :loading="loading" size="medium" type="primary" style="width:100%;" @click.native.prevent="handleReset" > <span v-if="!loading">Confirm changes</span> </el-button> </el-form-item> </el-form> <!-- Bottom --> <div class="el-register-footer"> <span>Copyright ? 2018-2021 ruoyi.vip All Rights Reserved.</span> </div> </div> </template> <script> import { getCodeImg } from "@/api/login"; import { resetUserProfilePwd } from "@/api/system/user"; import { setToken } from '@/utils/auth' export default { name: "Reset", data() { const equalToPassword = (rule, value, callback) => { if (this.resetForm.newPassword !== value) { callback(new Error("The passwords entered twice are inconsistent")); } else { const reg = /^(?![A-z0-9] + $)(?![A-z~!@#$%^ & amp;*()_ + ] + $)(?![0-9~ !@#$%^ & amp;*()_ + ] + $)([A-z0-9~!@#$%^ & amp;*()_ + ]{12,})/g if (!reg.test(value)) { callback(new Error("The entered password must contain numbers, uppercase and lowercase letters, and symbols")); } callback(); } }; return { codeUrl: "", resetForm: { username: "", oldPassword: "", newPassword: "", confirmPassword: "", code: "", sign: "", uuid: "" }, resetRules: { oldPassword: [ { required: true, trigger: "blur", message: "Old password cannot be empty" }, ], newPassword: [ { required: true, message: "New password cannot be blank", trigger: "blur" }, { min: 12, max: 20, message: "12 to 20 characters long", trigger: "blur" } ], confirmPassword: [ { required: true, message: "Confirm password cannot be empty", trigger: "blur" }, { required: true, validator: equalToPassword, trigger: "blur" } ], code: [{ required: true, trigger: "change", message: "Please enter the verification code" }] }, loading: false, captchaOnOff: true }; }, created() { this.getCode(); }, mounted() { // Clear the token first to prevent direct login after rollback, thereby bypassing the logic of forced password reset. setToken(''); // Get the parameters of the current link const params = this.$route.query; this.resetForm.sign = params.sign; this.resetForm.username = params.username; this.token = params.token; }, methods: { getCode() { getCodeImg().then(res => { this.captchaOnOff = res.captchaOnOff === undefined ? true : res.captchaOnOff; if (this.captchaOnOff) { this.codeUrl = "data:image/gif;base64," + res.img; this.resetForm.uuid = res.uuid; } }); }, handleReset() { this.$refs.resetForm.validate(valid => { if (valid) { this.loading = true; // Obtain and set the token obtained from the login page. Calling the interface requires a successful login token, otherwise it will prompt expiration. setToken(localStorage.getItem("reset_token")); resetUserProfilePwd(this.resetForm).then(res => { this.$alert("<font color='red'>Modification successful, please log in again</font>", 'System prompt', { dangerouslyUseHTMLString: true, type: 'success' }).then(() => { //Delete the token set by successful login localStorage.removeItem("reset_token"); //Clear token and force login setToken("") // Jump to login page this.$router.push("/login"); }).catch(() => {}); }).catch(() => { this.loading = false; if (this.captchaOnOff) { this.getCode(); } }) } }); } } }; </script> <style rel="stylesheet/scss" lang="scss"> .register { display: flex; justify-content: center; align-items: center; height: 100%; background-image: url("../assets/images/login-background.jpg"); background-size: cover; } .title { margin: 0px auto 30px auto; text-align: center; color: #707070; } .register-form { border-radius: 6px; background: #ffffff; width: 400px; padding: 25px 25px 5px 25px; .el-input { height: 38px; input { height: 38px; } } .input-icon { height: 39px; width: 14px; margin-left: 2px; } } .register-tip { font-size: 13px; text-align: center; color: #bfbfbf; } .register-code { width: 33%; height: 38px; float: right; img { cursor: pointer; vertical-align: middle; } } .el-register-footer { height: 40px; line-height: 40px; position: fixed; bottom: 0; width: 100%; text-align: center; color: #fff; font-family: Arial; font-size: 12px; letter-spacing: 1px; } .register-code-img { height: 38px; } </style>
The focus is on the handleReset function, please refer to the comments.
Password rules are 12-20 characters long and must contain numbers, uppercase and lowercase letters, and symbols.
4.Adjust src/router/index.js
Add a route to jump to the password reset page.
{ path: '/reset', component: (resolve) => require(['@/views/reset'], resolve), hidden: true }
5.Adjust src/views/login.vue
Adjust the handleLogin function and add logic to determine whether the password needs to be reset.
this.$store.dispatch("Login", this.loginForm).then((res) => { if (res.res_code & amp; & amp; res.res_code === 1001) {//Determine the reset password identification code returned by the backend interface // Set the token first localStorage.setItem("reset_token", res.token); // Redirect to the reset password page with verification parameters this.redirect = '/reset?' + 'sign=' + res.reset_sign + ' & amp;username=' + this.loginForm.username; } this.$router.push({ path: this.redirect || "/" }).catch(()=>{}); }).catch(() => { this.loading = false; if (this.captchaOnOff) { this.getCode(); } });
3. At the end
The difficulty of this requirement is how to force the login page to jump to the password reset page, but also prevent the user from being able to successfully log in to the backend when returning to the index address path, because the token has already been set at this time. However, this token cannot be cleared casually, because this token is needed when the password reset interface is called later.
Therefore, the difficulty of the entire requirement revolves around how to deal with the token after login. The final solution is to save the token in localStorage first, so that the token can be cleared when jumping to the reset password page to prevent users from bypassing Return to the index homepage and when calling the reset password interface, first get the token from localStorage, set it and then call the interface.
The knowledge points of the article match the official knowledge files, and you can further learn related knowledge. Java Skill TreeHomepageOverview 139213 people are learning the system