密码输入实时校验密码强度实现与加密传输方法总结

2,747 阅读6分钟

前言

最近项目中设计到修改密码功能,设计输入密码安全性校验与加密传输,就做一下总结,可供参考。

基本实现效果如下,当密码输入框聚焦后,弹出输入密码强度提示,随着输入的变化,会自动校验当前密码的安全级别,当全部满足校验规则时,再将密码加密后,传给后端。

这里主要总结一下,实时校验的实现方法,与加密方式。

实时校验实现方法

整体dialog搭建

<el-dialog title="修改密码" :area="600" :visible="changePasswordVisible" @close="close">
    <el-popover ref="popover" v-model="popShow" trigger="manual" placement="top" width="400">
      <div class="pwd-rule">
        <svg
          v-if="pwdSizeRule"
          t="1571050280880"
          class="icon"
          viewBox="0 0 1024 1024"
          version="1.1"
          xmlns="http://www.w3.org/2000/svg"
          p-id="5360"
          width="16"
          height="16"
        >
          <path
            d="M843.693959 293.609061 425.255869 712.056362 186.145026 472.947566 66.579883 592.504522 425.255869 951.165158 963.260126 413.174204Z"
            p-id="5361"
            fill="#02BF0F"
          ></path>
        </svg>
        <svg
          v-else
          t="1571050217123"
          class="icon"
          viewBox="0 0 1024 1024"
          version="1.1"
          xmlns="http://www.w3.org/2000/svg"
          p-id="4414"
          width="16"
          height="16"
        >
          <path
            d="M832 742.4 601.6 512 825.6 288 736 198.4 512 422.4 281.6 192 192 281.6 422.4 512 198.4 736 288 825.6 512 601.6 742.4 832Z"
            p-id="4415"
            fill="#FA3239"
          ></path>
        </svg>
        <span>{{ PWD_SIZE_RULE }}</span>
      </div>
      <div class="pwd-rule">
        <svg
          v-if="pwdCharRule"
          t="1571050280880"
          class="icon"
          viewBox="0 0 1024 1024"
          version="1.1"
          xmlns="http://www.w3.org/2000/svg"
          p-id="5360"
          width="16"
          height="16"
        >
          <path
            d="M843.693959 293.609061 425.255869 712.056362 186.145026 472.947566 66.579883 592.504522 425.255869 951.165158 963.260126 413.174204Z"
            p-id="5361"
            fill="#02BF0F"
          ></path>
        </svg>
        <svg
          v-else
          t="1571050217123"
          class="icon"
          viewBox="0 0 1024 1024"
          version="1.1"
          xmlns="http://www.w3.org/2000/svg"
          p-id="4414"
          width="16"
          height="16"
        >
          <path
            d="M832 742.4 601.6 512 825.6 288 736 198.4 512 422.4 281.6 192 192 281.6 422.4 512 198.4 736 288 825.6 512 601.6 742.4 832Z"
            p-id="4415"
            fill="#FA3239"
          ></path>
        </svg>
        <span>{{ PWD_TYPE_RULE }}</span>
      </div>
    </el-popover>
    <el-form ref="form" :model="form" :rules="rules" label-width="120px" label-position="top">
      <el-form-item label="登录账户密码" prop="oldPwd">
        <el-input
          v-model="form.oldPwd"
          v-popover:popover
          :type="isShowOldPwd ? 'text' : 'password'"
          placeholder="请输入登录账户密码"
        >
          <i
            slot="suffix"
            class="el-input__icon"
            :class="isShowOldPwd ? 'h-icon-password_visible' : 'h-icon-password_unvisible'"
            @mousedown="isShowOldPwd = !isShowOldPwd"
            @mouseup="isShowOldPwd = !isShowOldPwd"
          ></i>
        </el-input>
      </el-form-item>

      <el-form-item
        label="新密码"
        prop="newPwd"
        :style="{ 'margin-bottom': `${form.newPwd ? '4px' : ''}` }"
      >
        <el-input
          v-model="form.newPwd"
          v-popover:popover
          :type="isShowNewPwd ? 'text' : 'password'"
          placeholder="请输入新密码"
          @focus="popShow = true"
          @blur="popShow = false"
        >
          <i
            slot="suffix"
            class="el-input__icon"
            :class="isShowNewPwd ? 'h-icon-password_visible' : 'h-icon-password_unvisible'"
            @mousedown="isShowNewPwd = !isShowNewPwd"
            @mouseup="isShowNewPwd = !isShowNewPwd"
          ></i>
        </el-input>
       <div class="pwd-level" v-show="!!form.newPwd && !newPwdError">
            <div class="level-process">
                <span v-for="(item, index) in 4" class="level-process-block" :style="{background:`${index>level?`#EDEFF4`:styleColor[level]}`}" :key="index"></span>
            </div>
            <div class="level-tip" :style="{color:tipsColor[level]}">{{tipsText[level]}}</div>
        </div>
      </el-form-item>
      <el-form-item label="重复新密码" prop="confirmPwd">
        <el-input
          v-model="form.confirmPwd"
          :type="isShowConfirmPwd ? 'text' : 'password'"
          placeholder="请再次输入密码"
        >
          >
          <i
            slot="suffix"
            class="el-input__icon"
            :class="isShowConfirmPwd ? 'h-icon-password_visible' : 'h-icon-password_unvisible'"
            @mousedown="isShowConfirmPwd = !isShowConfirmPwd"
            @mouseup="isShowConfirmPwd = !isShowConfirmPwd"
          ></i>
        </el-input>
      </el-form-item>
    </el-form>
    <div slot="footer" class="dialog-footer">
      <el-button type="primary" @click="handleChange">
        确定
      </el-button>
      <el-button @click="close">
        取消
      </el-button>
    </div>
  </el-dialog>

当输入聚焦时,弹出上册el-popover,提示密码输入要求,当开始输入时,实时校验密码是否满足要求。下面主要列出输入新密码部分实时校验逻辑实现。

<script>
import {
  OLDPWD_EMPTY_ERROR,
  OLD_PWD_EMPTY,
  NEWPWD_EMPTY_ERROR,
  NEW_PWD_EMPTY,
  CONFIRMPWD_EMPTY_ERROR,
  CONFIRM_PWD_EMPTY,
  NOT_EQUAL_ERROR,
  PWD_CHAR_ERROR,
  PWD_SIZE_ERROR,
  PWD_RULE_ERROR,
  PWD_TYPE_RULE,
  PWD_SIZE_RULE,
  PWD_CHANGE_SUCCESS,
  NEW_OLD_EQUAL
} from './constant.js'

export default {
  name: 'EitsModPwd',
  model: {
    prop: 'changePasswordVisible',
    event: 'changeVisiblePwd'
  },
  props: {
    changePasswordVisible: Boolean,
  },
  data() {
    const validateNewPwd = (rule, value, callback) => {
      this.pwdSizeRule = this.checkPwdSize(value)
      this.pwdCharRule = this.checkPwdChar(value)
      // eslint-disable-next-line no-control-regex
      if (value.match(/[^\x00-\xff]/)) {
        this.newPwdError = true
        callback(PWD_CHAR_ERROR)
      } else {
        this.newPwdError = false
        callback()
      }
    }
    return {
    tipsColor: ["#FE5332", "#FF952C", "#FFCC00", "#3BCD8D"],
            tipsText: ["危险", "弱", "中", "强"],
            styles: ["danger", "warning", "proper", "strong"],
            styleColor: ["#FE5332", "#FF952C", "#FFCC00", "#3BCD8D"],
            
      OLD_PWD_EMPTY: OLD_PWD_EMPTY,
      NEWPWD_EMPTY_ERROR: NEWPWD_EMPTY_ERROR,
      NEW_PWD_EMPTY: NEW_PWD_EMPTY,
      CONFIRMPWD_EMPTY_ERROR: CONFIRMPWD_EMPTY_ERROR,
      CONFIRM_PWD_EMPTY: CONFIRM_PWD_EMPTY,
      NOT_EQUAL_ERROR: NOT_EQUAL_ERROR,
      PWD_CHAR_ERROR: PWD_CHAR_ERROR,
      PWD_SIZE_ERROR: PWD_SIZE_ERROR,
      PWD_RULE_ERROR: PWD_RULE_ERROR,
      PWD_TYPE_RULE: PWD_TYPE_RULE,
      PWD_SIZE_RULE: PWD_SIZE_RULE,
      PWD_CHANGE_SUCCESS: PWD_CHANGE_SUCCESS,
      NEW_OLD_EQUAL: NEW_OLD_EQUAL,
      form: {
        oldPwd: '',
        newPwd: '',
        confirmPwd: ''
      },
      rules: {
        newPwd: [
          { required: true, message: NEWPWD_EMPTY_ERROR, trigger: 'blur + change' },
          { validator: validateNewPwd, trigger: 'blur + change' },
          { validator: validateNewPwd2, trigger: 'blur' }
        ]
      },
      isShowOldPwd: false,
      isShowNewPwd: false,
      isShowConfirmPwd: false,
      oldPwdError: false,
      newPwdError: false,
      confirmPwdError: false,
      pwdSizeRule: false,
      pwdCharRule: false,
      popShow: false
    }
  },
   computed: {
        level () {
            return getPwdRank(this.form.newPwd);
        }
    }
  methods: {
     /**
     * @desc 校验密码长度方法
     */
    checkPwdSize: pwd => pwd.length >= 8 && pwd.length <= 20,
     /**
     * @desc 校验密码规范方法
     */
    checkPwdChar: pwd => {
      let record = 0
      ;/(?=[\x21-\x7e]+)[^A-Za-z0-9]/g.test(pwd) && record++
      ;/[a-z]/g.test(pwd) && record++
      ;/[A-Z]/g.test(pwd) && record++
      ;/[0-9]/g.test(pwd) && record++
      return !(record < 2)
    }
  }
}
</script>

<style lang="scss" scoped>


.pwd-level {
    position: relative;
    height: 20px;
    line-height: 20px;
    width: calc(100%);
    display: flex;
    justify-content: space-between;
    .level-process {
        position: relative;
        width: calc(100% - 32px);
        height: 4px;
        margin-top: 8px;
        display: flex;
        align-items: center;
        span.level-process-block {
            position: relative;
            height: 4px;
            margin-right: 3px;
            background: #EDEFF4;
            flex: 1;
        }
    }
    .level-tip {
        position:relative;
        width: 25px;
        text-align: center;
        font-size: 12px;
    }
}
.pwd-rule {
  svg,
  span {
    vertical-align: middle;
  }
}
</style>

其中引入说明部分如下:

export const OLDPWD_EMPTY_ERROR = '登录账户密码不能为空'

export const OLD_PWD_EMPTY = '请商户如登录账户密码'

export const NEWPWD_EMPTY_ERROR = '请输入新密码'

export const NEW_PWD_EMPTY = '请输入新密码'

export const CONFIRMPWD_EMPTY_ERROR = '请再次输入密码'

export const CONFIRM_PWD_EMPTY = '请再次输入密码'

export const NOT_EQUAL_ERROR = '两次输入密码不一致'

export const PWD_CHAR_ERROR = '密码只能包含数字字母特殊字符'

export const PWD_SIZE_ERROR = '请输入8位~20位密码'

export const PWD_RULE_ERROR = '至少包含以下2种字符:大小写字母、数字、特殊字符'

export const PWD_TYPE_RULE = '至少包含以下2种字符:大小写字母、数字、特殊字符'

export const PWD_SIZE_RULE = '密码长度为8位~20位'

export const PWD_CHANGE_SUCCESS = '密码修改成功,请重新登录'

export const NEW_OLD_EQUAL = '新密码不能是当前登录密码'

实时监测密码强度实现方法

 /**
 * @desc 当只输入一种种字符类型,密码强度为危险,校验不通过
         当只输入两种字符,两种字符分别为字母或数字,则密码强度为弱,其他两种的组合,则强度为中
         当输入密码有三种字符组合,则密码强度为强
 */
getPwdRank = (szPwd, szUser = '') => {
  let iRank = 0
  szPwd.match(/[a-z]/g) && iRank++
  szPwd.match(/[A-Z]/g) && iRank++
  szPwd.match(/[0-9]/g) && iRank++
  szPwd.match(/[^a-zA-Z0-9]/g) && iRank++
  iRank = iRank > 3 ? 3 : iRank
  if (
    szPwd.length < 8 ||
    iRank === 1 ||
    szPwd === szUser ||
    szPwd ===
      szUser
        .split('')
        .reverse()
        .join('')
  ) {
    iRank = 0
  }
  if (iRank === 2) {
    if (
      (szPwd.match(/[0-9]/g) && szPwd.match(/[a-z]/g)) ||
      (szPwd.match(/[0-9]/g) && szPwd.match(/[A-Z]/g))
    ) {
      iRank = 1
    }
  }
  return iRank
}

到此密码实时校验部分已完成,保证用户输入的密码的安全级别能达到要求。

密码加密实现

这里主要采用CryptoJS类库的SHA256加密方式 首先下载CryptoJS类库:npm install crypto-js,在方法中引入

import CryptoJS from 'crypto-js'

这里对保存密码和验证密码,用了两种方式,保证其传输安全性

/**
 * SHA256加密方法,用于验证密码
 * @param password 密码
 * @param salt 加密的盐
 * @param vCode 挑战码
 * @returns {String}  加密后密码
 */
pwdHashEncrypt = (password, salt, vCode) => {
  return CryptoJS.SHA256(CryptoJS.SHA256(password + salt).toString() + vCode).toString()
}
/**
 * 通用用户密码SHA256加密方法,用于保存密码
 * @param password 密码
 * @param salt 加密的盐
 * @returns {String}  加密后密码
 */
pwdHashEncryptSave = (password, salt) => {
  return CryptoJS.SHA256(password + salt).toString()
}

salt为16为随机数,vCode挑战码通过后端接口获取

const salt = this.randomn(16).toString()
/**
 * @author liujie22
 * @desc 获取16位随机数
 */
randomn(n) {
  return parseInt((Math.random() + 1) * Math.pow(10, n - 1))
},

到此,密码完成加密。至于SHA256的算法的详解,待下一次内容。