若依-用户长时间未修改密码强制修改

需求原因

用户长期未修改密码可能存在密码安全问题,项目要求提供一个配置使用户一定时间间隔就修改密码

要求

后端登录时判断是否达到最大间隔时间,达到则修改用户的constraint字段(方便前端判断);
登录验证后,判断是否需要修改密码,需要修改则阻止跳转、弹出修改框;
限制:新密码不能与旧密码一致;
修改完成后正常跳转到主页面;
数据库更新密码、维护constraint字段。

后端实现

判断是否要求强制修改密码

src\main\java\com\ruoyi\framework\web\service\UserDetailsServiceImpl.java

修改前
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException
{
    SysUser user = userService.selectUserByUserName(username);
    if (StringUtils.isNull(user))
    {
        log.info("登录用户:{} 不存在.", username);
        throw new ServiceException("登录用户:" + username + " 不存在");
    }
    else if (UserStatus.DELETED.getCode().equals(user.getDelFlag()))
    {
        log.info("登录用户:{} 已被删除.", username);
        throw new ServiceException("对不起,您的账号:" + username + " 已被删除");
    }
    else if (UserStatus.DISABLE.getCode().equals(user.getStatus()))
    {
        log.info("登录用户:{} 已被停用.", username);
        throw new ServiceException("对不起,您的账号:" + username + " 已停用");
    }

    return createLoginUser(user);
}
修改后
// 新增自动装配
@Autowired
private SysConfigMapper sysConfigMapper;

@Autowired
private SysUserMapper userMapper;

// 代码修改
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException
{
    SysUser user = userService.selectUserByUserName(username);
    if (StringUtils.isNull(user))
    {
        log.info("登录用户:{} 不存在.", username);
        throw new ServiceException("登录用户:" + username + " 不存在");
    }
    else if (UserStatus.DELETED.getCode().equals(user.getDelFlag()))
    {
        log.info("登录用户:{} 已被删除.", username);
        throw new ServiceException("对不起,您的账号:" + username + " 已被删除");
    }
    else if (UserStatus.DISABLE.getCode().equals(user.getStatus()))
    {
        log.info("登录用户:{} 已被停用.", username);
        throw new ServiceException("对不起,您的账号:" + username + " 已停用");
    }
    // 用户信息没有密码最后修改日期,则直接强制修改密码
    if (user.getPassTime() == null) {
        user.setConstraint(true);
    } else {
        // 有最后修改日期则再进行判断
        // 获取最大密码修改时间间隔(毫秒值)
        // 若依已经写好的方法,直接拿来用就行
        SysConfig sysConfig = sysConfigMapper.checkConfigKeyUnique("sys.user.pass.interval");
        Long interval = Long.parseLong(sysConfig.getConfigValue()) * 24 * 60 * 60 * 1000;
        // 获取用户密码修改间隔
        Long userInterval = System.currentTimeMillis() - user.getPassTime().getTime();
        // 如果用户间隔超过间隔上限则要求强制修改密码
        if (userInterval >= interval) {
            user.setConstraint(true);
        }
    }
    // 保存到数据库
    // 若依已经写好的用户信息更新
    userMapper.updateUser(user);
    return createLoginUser(user);
}

通过constraint判断修改密码场景(强制还是自主)

src\main\java\com\ruoyi\web\controller\system\SysProfileController.java

修改前
	// 修改前源码
	// Javabean记得新增constraint属性和get、set
	// userMapper.xml记得修改一下返回字段
	/**
     * 重置密码
     */
    @Log(title = "个人信息", businessType = BusinessType.UPDATE)
    @PutMapping("/updatePwd")
    public AjaxResult updatePwd(String oldPassword, String newPassword)
    {
        LoginUser loginUser = getLoginUser();
        String userName = loginUser.getUsername();
        String password = loginUser.getPassword();
        if (!SecurityUtils.matchesPassword(oldPassword, password))
        {
            return AjaxResult.error("修改密码失败,旧密码错误");
        }
        if (SecurityUtils.matchesPassword(newPassword, password))
        {
            return AjaxResult.error("新密码不能与旧密码相同");
        }
        if (userService.resetUserPwd(userName, SecurityUtils.encryptPassword(newPassword)) > 0)
        {
            // 更新缓存用户密码
            loginUser.getUser().setPassword(SecurityUtils.encryptPassword(newPassword));
            tokenService.setLoginUser(loginUser);
            return AjaxResult.success();
        }
        return AjaxResult.error("修改密码异常,请联系管理员");
    }
修改后
	// 修改后代码
	/**
     * 重置密码
     */
    @Log(title = "个人信息", businessType = BusinessType.UPDATE)
    @PutMapping("/updatePwd")
    public AjaxResult updatePwd(@RequestBody Map<String, Object> user) {
    	// 获取constraint
        boolean constraint = (boolean) user.get("constraint");
        // 防止传参异常或维护时出现问题
        constraint = constraint == true ? constraint : false;
        LoginUser loginUser = getLoginUser();
        String userName = loginUser.getUsername();
        String password = loginUser.getPassword();
        // 获取旧密码(为了不影响若依中原有的密码修改)
        String oldPassword = (String) user.get("oldPassword");
        // 获取新密码
        String newPassword = (String) user.get("newPassword");
        // 强制修改密码时不用判断旧密码
        if (!constraint && !SecurityUtils.matchesPassword(oldPassword, password)) {
            System.out.println("旧密码");
            return AjaxResult.error("修改密码失败,旧密码错误");
        }
        if (SecurityUtils.matchesPassword(newPassword, password)) {
            System.out.println("新密码");
            return AjaxResult.error("新密码不能与旧密码相同");
        }
        if (userService.resetUserPwd(userName, SecurityUtils.encryptPassword(newPassword)) > 0) {
            // 更新缓存用户密码
            loginUser.getUser().setPassword(SecurityUtils.encryptPassword(newPassword));
            tokenService.setLoginUser(loginUser);
            return AjaxResult.success();
        }
        return AjaxResult.error("修改密码异常,请联系管理员");
    }

逻辑改变不大:
新增判断——强制修改时不比判断旧密码(传空就行);
接收类型:
这是由于开发过程中出现了一些问题、bug,排查过程中将入参类型修改了一下;
新增接收参数:constraint(true:强制修改密码,false:不强制)

前端实现

ruoyi-ui\src\views\login.vue

// 新增弹出框标签
<el-dialog title="您的密码已长时间未修改,请修改密码" :visible.sync="loginInfo.constraint">
  <el-form>
    <el-form-item label="新密码">
      <el-input v-model="newPassword" autocomplete="off"></el-input>
    </el-form-item>
  </el-form>
  <div slot="footer" class="dialog-footer">
    <el-button type="primary" @click="updatePwd">确 定</el-button>
  </div>
</el-dialog>

// 导入方法
import { updateUserPwd } from "@/api/system/user";

//新增对象、变量
loginInfo:{
  constraint: false
},
newPassword:'',

// 修改登录方法
handleLogin() {
      this.$refs.loginForm.validate((valid) => {
        if (valid) {
          this.loading = true;
          if (this.loginForm.rememberMe) {
            Cookies.set("username", this.loginForm.username, { expires: 30 });
            Cookies.set("password", encrypt(this.loginForm.password), {
              expires: 30,
            });
            Cookies.set("rememberMe", this.loginForm.rememberMe, {
              expires: 30,
            });
          } else {
            Cookies.remove("username");
            Cookies.remove("password");
            Cookies.remove("rememberMe");
          }
          this.$store
            .dispatch("Login", this.loginForm)
            .then((res) => {
              getInfo().then(loginInfo => {
                this.loginInfo = loginInfo.user
                // 如果返回的constraint为false则正常跳转到主页面
                if(!this.loginInfo.constraint) {
                  this.$router.push({ path: this.redirect || "/" }).catch(() => {});
                } else {
                  // 否则设置constraint值为true,使页面不能跳转到主页面
                  localStorage.setItem('constraint', true)
                }
              }).catch(() => {})
              // const md5passwod = md5(this.loginForm.password);
              // console.log("md5passwod" + md5passwod);
              //用于后续解锁
              // localStorage.setItem("username", this.loginForm.username);
           
            })
            .catch(() => {
              this.loading = false;
              if (this.captchaOnOff) {
                this.getCode();
              }
            });
        }
      });
    },
// 新增修改方法
updatePwd(){
  updateUserPwd("", this.newPassword, true).then(response => {
  	// 修改完成将constraint改为false
    localStorage.setItem('constraint', false)
    this.$modal.msgSuccess("修改成功");
    this.$router.push({ path: this.redirect || "/" }).catch(() => {});
      })
  .catch((e) => {
    console.info(e)
  });
},

ruoyi-ui\src\api\system\user.js

// 新增constraint属性
// 用户密码重置
export function updateUserPwd(oldPassword, newPassword, constraint) {
  const data = {
    oldPassword,
    newPassword,
    constraint
  }
  return request({
    url: '/system/user/profile/updatePwd',
    method: 'put',
    data: data
  })
}

ruoyi-ui\src\permission.js
修改前

if (getToken()) {
  to.meta.title && store.dispatch('settings/setTitle', to.meta.title)
  /* has token*/
  if (to.path === '/login') {
    next({ path: '/' })
    NProgress.done()
// 修改后
if (getToken()) {
    to.meta.title && store.dispatch('settings/setTitle', to.meta.title)
    /* has token*/
    // localStorage.setItem('constraint',false)
    if (to.path === '/login') {
      // 新增判断——当前位置未登录页面时:如果constraint值为true
      if(localStorage.getItem("constraint") == 'true') {
      	// 去掉token
        setToken("")
      }
      // 不为true时不变
      next({ path: '/' })
      NProgress.done()

ruoyi-ui\src\views\system\user\profile\resetPwd.vue

submit() {
  this.$refs["form"].validate(valid => {
    if (valid) {
      // 修改updateUserPwd方法的入参
      // constraint值为true时不校验旧密码,传入false校验,不影响功能
      updateUserPwd(this.user.oldPassword, this.user.newPassword, false).then(response => {
        this.$modal.msgSuccess("修改成功");
      });
    }
  });
},

展示

在这里插入图片描述

遇到的问题

用户不必更改密码,直接刷新页面就会进入到主页面

这样一来我做的强制修改密码就不强制了,那怎么忍得了,这还是我一时兴起点了一下刷新试了试,结果果然直接跳过强制修改了

解决方案、想法

如果constraint字段为true(即需要强制修改密码),将token设空,这样刷新就不会跳转到主页面

但是这样处理的时候就出现了一个问题(困扰了大半个小时,改了不少地方——尤其是后端的接收类型我都改成Map了……)
在这里插入图片描述
没错!修改密码这里需要token的校验,我把token清空了就无法正常调用了

解决(1)

在这里插入图片描述

在这里插入图片描述

解决(2)

在这里插入图片描述
在这里插入图片描述

新增需求

密码强度校验级别

src\main\java\com\ruoyi\web\controller\system\SysProfileController.java

// 新增代码
// 获取密码强度
SysConfig sysConfig = configMapper.checkConfigKeyUnique("sys.user.pass.strength");
String strength = sysConfig.getConfigValue();
Pattern p;
boolean strengthEnough = false;
switch (strength) {
    // 强度3:必须含有特殊字符
    case "3":
        // 非数字、字母
        p = Pattern.compile("[A-Za-z0-9]");
        for (int i = 0; i < newPassword.length(); i++) {
            if (!p.matcher(newPassword.substring(i, i + 1)).find()) {
                strengthEnough = true;
                break;
            }
        }
        if (!strengthEnough) {
            return AjaxResult.error("新密码必须含特殊字符");
        }
    // 强度2:必须含有大写和小写
    case "2":
        strengthEnough = false;
        // 含大写字母
        p = Pattern.compile("^[A-Z]+$");
        for (int i = 0; i < newPassword.length(); i++) {
            if (p.matcher(newPassword.substring(i, i + 1)).find()) {
                // 含小写字母
                p = Pattern.compile("^[a-z]+$");
                for (int j = 0; j < newPassword.length(); j++) {
                    if (p.matcher(newPassword.substring(j, j + 1)).find()) {
                        strengthEnough = true;
                        break;
                    }
                }
                // 通过直接结束循环
                if (strengthEnough) {
                    break;
                }
            }
        }
        if (!strengthEnough) {
            return AjaxResult.error("新密码必须含大写和小写");
        }
    // 强度1:密码至少6位
    case "1":
        if (newPassword.length() < 6) {
            return AjaxResult.error("新密码至少6位");
        }
}
  • 12
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 20
    评论
实现用户首次登录时需要修改密码可以通过配置PAM(Pluggable Authentication Modules)实现。PAM是一种模块化的身份验证机制,可以让系统管理员灵活地配置各种身份验证和授权方案。 在CentOS 6/7上,可以通过以下步骤实现用户首次登录时需要修改密码: 1. 安装必要的软件包 在终端中执行以下命令安装必要的软件包: ``` yum install cracklib-dicts pam_cracklib ``` 2. 配置PAM 编辑/etc/pam.d/system-auth文件,添加以下一行: ``` password requisite pam_cracklib.so try_first_pass retry=3 minlen=8 lcredit=-1 ucredit=-1 dcredit=-1 ocredit=-1 ``` 上述配置采用pam_cracklib模块来强制密码复杂度,其中: - try_first_pass表示优先使用用户输入的密码; - retry表示输入错误时允许重试的次数; - minlen表示密码最小长度; - lcredit、ucredit、dcredit和ocredit分别表示密码中必须包含小写字母、大写字母、数字和特殊字符的数量。 3. 配置密码策略 编辑/etc/login.defs文件,设置以下参数: ``` PASS_MAX_DAYS 90 PASS_MIN_DAYS 0 PASS_WARN_AGE 14 ``` 上述配置分别表示: - PASS_MAX_DAYS表示密码有效期,单位为天; - PASS_MIN_DAYS表示两次密码修改之间的最小时间间隔,单位为天; - PASS_WARN_AGE表示密码过期前提醒用户的时间,单位为天。 4. 测试 在客户端上创建一个新用户,然后使用该用户登录系统,第一次登录时会提示修改密码。 通过以上步骤,就可以实现在客户端处实现用户首次登录时需要修改密码的功能。需要注意的是,如果使用LDAP进行身份验证,则需要在LDAP服务器上配置密码策略,并确保与客户端的配置一致。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 20
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值