如何用Vue创建一个密码强度检查器

765 阅读12分钟

我打赌你已经注意到,今天的大多数应用程序要求你设置一个符合特定规则的密码。这些规则可能是最小长度、包括或不包括特殊字符、大写或小写字母等--在大多数情况下,这几条规则就足以确保你的密码很强大。但我们可以更进一步,使用一个叫做 zxcvbn的库来检查我们的密码强度。

什么是zxcvbn?

zxcvbn 是一个受密码破解者启发的密码强度估算器。通过模式匹配和保守估计,它可以识别和权衡40000个常见的密码,常见的名字姓氏,维基百科上的流行词,不同语言和不同国家的常见词,以及其他常见模式,如日期,重复( ),序列( ),键盘模式( )等等。aaa``abcd``qwertyuiop

Our demo password strength checker

以下是密码检查器的几项工作,我们将在这篇文章中介绍。

  • 允许用户输入任何密码
  • 在每次改变输入时检查密码得分
  • 使用彩色的进度条和信息提供视觉反馈
  • 当达到指定分数时启用提交按钮

在这篇文章中你会发现

在这篇文章中,我想告诉你如何。

  1. 用Tailwind CSS创建一个基本的表单模板
  2. 用Tailwind CSS建立密码得分组件zxcvbn
  3. 处理按钮状态
  4. 在表单中添加一个彩色编码的动画进度条

我在StackBlitz上设置了一个演示,这样你就可以看到我们的目标。

好的,这就是我们想要的,所以让我们来实现它吧

第1步:用Tailwind CSS创建一个基本的表单模板

让我们首先为表单创建一个基本模板。我们将使用Tailwind CSS来使造型更容易、更快速。

在我们的PasswordInputForm 组件的模板部分,添加一个input 和一个button

<!-- PasswordInputForm.vue -->
<template>
  <div class="container mx-auto w-64">
    <input
      class="block w-full my-1 p-2 border border-grey-30 rounded leading-normal text-grey-80"
      type="text"
    />
    <button
      class="mt-6 px-6 py-1 leading-lg select-none bg-blue-400 text-white font-semibold border border-blue-500 rounded"
    >
      Submit
    </button>
  </div>
</template>

现在我们有了一些标记可以使用,让我们给我们的组件添加一些属性,让我们能够使它更有互动性。在我们组件的script 部分,添加passwordisPasswordStrong 属性,这两个属性都是反应式的、可变的ref 对象

// PasswordInputForm.vue
<script>
import { ref } from "vue";

export default {
  setup() {
    const password = ref("");
    const isPasswordStrong = ref(false);

    return { password, isPasswordStrong };
  },
};
</script>

在这个阶段,我们需要做的最后一件事是将新创建的属性绑定到模板上。我们使用v-model 指令将我们的password 属性绑定到input 元素上,这允许我们在用户每次改变输入时跟踪当前值,并将该值分配给我们的反应性password ref。

<!-- PasswordInputForm.vue -->
<input
  class="block w-full my-1 p-2 border border-grey-30 rounded leading-normal text-grey-80"
  type="text"
  v-model="password"
/>

我们还可以使用isPasswordStrong ,根据道具的值来启用或禁用我们演示中的蓝色提交按钮。我们可以通过有条件地给按钮设置适当的类来做到这一点。

<!-- PasswordInputForm.vue -->
<button
  class="mt-6 px-6 py-1 leading-lg select-none bg-blue-400 text-white font-semibold border border-blue-500 rounded"
  :class="[
    {
      'cursor-not-allowed opacity-50 pointer-events-hover': !isPasswordStrong,
    },
  ]"
>
  Submit
</button>

完成这些后,我们最终得到一个基本的模板,看起来像这样。

Our basic password strength checker template

密码强度检查器 - 第1步 - StackBlitz

Vue.js的入门项目

第2步:用以下方法构建密码评分组件zxcvbn

我们的输入已经准备好了。当我们改变输入时,它会反映出组件实例上的相应属性。让我们来实现代码逻辑,让我们得到密码的分数。

我们将把所有这些逻辑封装在一个单独的组件中,称为PasswordScore 。在这一步,我们还将向用户显示一条信息,表明当前密码的强度。

和以前一样,我们将用一个简单的模板开始我们的组件。

<!-- PasswordScore.vue -->
<template>
  <div class="relative select-none">
    <p class="absolute mt-1 text-sm">
      // Here we will show the message to the user
    </p>
  </div>
</template>

script 部分,添加一个props选项,定义我们的组件将从外部世界接受什么。我们只需要一个value 道具来告诉我们当前用户的输入是什么,所以我们可以将该属性标记为required - 这将迫使我们总是将该属性从父组件传递给子组件。

<!-- PasswordScore.vue -->
<script>
export default {
  props: {
    value: {
      type: String,
      required: true,
    },
  }
}
</script>

我们在组件中拥有这个值。现在,我们需要使用zxcvbn 库来检索该值的分数。

第一步是将所需的库元素导入我们的组件中。我们在我们的script 部分的顶部这样做。

<!-- PasswordScore.vue -->
<script>
import { zxcvbn, zxcvbnOptions } from "@zxcvbn-ts/core";
import zxcvbnCommonPackage from "@zxcvbn-ts/language-common";
import zxcvbnEnPackage from "@zxcvbn-ts/language-en";

export default {
  ...
}
</script>

接下来,我们需要通过设置适当的选项来初始化该库。我们希望在组件被创建时立即初始化库。为了在组件创建时启动逻辑,我们只需要把它放在该组件的setup 函数中。关于可以传递给zxcvbn的可用选项的详细描述,我建议查看zxcvbn文档。

// PasswordScore.vue
export default {
  ...

  setup() {
    const options = {
        dictionary: {
          ...zxcvbnCommonPackage.dictionary,
          ...zxcvbnEnPackage.dictionary,
        },
        graphs: zxcvbnCommonPackage.adjacencyGraphs,
        translations: zxcvbnEnPackage.translations,
      };
      zxcvbnOptions.setOptions(options);
    }
}
</script>

使用zxcvbn分数库

好了,我们都准备好了,可以开始使用分数库了。让我们停下来想一想,我们要做的是:在每次输入值发生变化时,获得输入值的分数。

基于另一个被动的值来获得一个值的最好方法是使用一个计算属性。因此,让我们定义一个名为score 的计算属性。从Vue导入一个computed 帮助器,并使用该帮助器给一个const 赋值。

// PasswordScore.vue
export default {
  import { computed } from "vue";
  ...

  setup(props) {
    ...
    const score = computed(() => {
      const hasValue = props.value && props.value.length > 0;

      if (!hasValue) {
        return 0;
      }

      return zxcvbn(props.value).score + 1;
    });
    ...
  }
  ...
}

在计算属性中,首先检查该值是否存在,或者它是否是一个空字符串。如果后者是true ,我们可以为自己节省一些时间,直接返回0

如果值不是空的,我们使用zxcvbn ,把我们的value 传递给它。注意我们在这里是如何使用props的--props作为第一个参数被传递给setup 函数,然后使用props.xyz 。然后zxcvbn将返回一个带有score 属性的对象。

很好!我们的score 计算的属性将在每次我们的输入值变化时重新计算,并为我们返回一个分数值。但是我们如何向用户显示这个值呢?

显示zxcvbn的分数值

让我们再实现两个计算属性,以帮助我们向用户显示一个信息,表明当前的分数。

第一个将保存所有我们可以显示给用户的潜在描述和颜色代码。

// PasswordScore.vue
export default {
  import { computed } from "vue";
  ...

  setup(props) {
    ...
    const descriptions = computed(() => [
      {
        color: "bg-red-600",
        label: "Weak, my 2 years old son can break it!",
      },
      { color: "bg-red-300", label: "Still weak, keep on trying!" },
      { color: "bg-yellow-400", label: "We are getting there..." },
      { color: "bg-green-200", label: "Nice, but you can still do better" },
      {
        color: "bg-green-400",
        label: "Congratulations, you made it!",
      },
    ]);
    ...
  }
  ...
}

第二个提取并返回我们想显示给用户的描述。其工作原理与上述相同:首先,我们检查该值是否存在,并且不是一个空字符串;然后,如果它存在,我们从描述数组中返回相应的元素。如果它不存在,我们就返回一个通用信息,以鼓励用户使用该输入。

// PasswordScore.vue
export default {
  import { computed } from "vue";
  ...

  setup(props) {
    ...
    const description = computed(() =>
      props.value && props.value.length > 0
        ? descriptions.value[score.value - 1]
        : {
            color: "bg-transparent",
            label: "Start typing to check your password",
          }
    );
    ...
  }
  ...
}

好了,现在我们已经准备好了描述,我们仍然需要把它展示给用户。让我们使用Vue的模板语法来做。

<!-- PasswordScore.vue -->
<template>
  <div class="relative select-none">
    <p class="absolute mt-1 text-sm">
      {{ description.label }}
    </p>
  </div>
</template>

还有一件事我们需要记住:在设置函数中定义的每个属性,如果我们想在模板中使用,也需要由设置函数返回。

// PasswordScore.vue
export default {
  import { computed } from "vue";
  ...

  setup(props) {
    ...
    return { description };
    ...
  }
  ...
}

由于我们想在PasswordInputForm 组件中使用我们的PasswordScore 组件,我们需要以导入分数库的同样方式导入PasswordScore 组件,并使用components 选项注册它,这使它对组件实例可用。

<!-- PasswordInputForm.vue -->
<script>
...
import PasswordScore from './PasswordScore.vue';

export default {
  components: {
    PasswordScore,
  },
  ...
};
</script>

然后,我们可以在模板部分使用该组件,就像下面这样。

<!-- PasswordInputForm.vue -->
<template>
  <div class="container mx-auto w-64">
    ...
    <PasswordScore
      :value="password"
      class="mt-2 mb-6"
    />
    ...
  </div>
</template>

这就是第二步的全部内容!这一步之后,我们应该看到这样的东西。

Our password strength checker with the strength message

密码强度检查器 - 第二步 - StackBlitz

Vue.js的入门项目

在下一步,我们将处理提交按钮,使其在分数达到我们的标准时可以点击。

第3步:处理按钮的状态

在这一步中,当分数的值≥4 ,我们将启用提交按钮,而当它低于这个阈值时,又将禁用它。

要做到这一点,我们需要检测我们的阈值何时被满足。这也是我们可以用一个计算的属性来做的事情

这一次,我们将把它称为isPasswordStrong ,我强烈建议对所有布尔类型的变量使用这一命名规则,因为从长远来看,它使编码和阅读代码变得更加容易。

// PasswordScore.vue
export default {
  import { computed } from "vue";
  ...

  setup(props) {
    ...
    const isPasswordStrong = computed(() => score.value >= 4);
    ...
  }
  ...
}

现在我们有了这个值,我们需要与父组件沟通,这个值发生了变化,按钮应该根据我们设置的阈值被启用或禁用。我们可以通过发射一个事件来做到这一点。

要发射一个事件,我们需要首先用我们组件上的emits 属性来定义它。最终,我们将定义两个事件。

  1. 当密码变强时 (passed)
  2. 当密码变弱时 (failed)
// PasswordScore.vue
export default {
  import { computed } from "vue";
  ...

  setup(props) {
    ...
    emits: ["passed", "failed"],
    ...
  }
  ...
}

让我们继续设置,以便当isPasswordStrong 的值发生变化时,我们发出这些事件。我们不能在计算属性里面这样做,因为计算属性不应该引起任何副作用--而在我们的例子中,发射一个事件和改变一个按钮状态就是一个副作用。

使用Vue观察器

但是,不要害怕!Vue提供了合适的工具。Vue为这种情况提供了合适的工具:观察器。观察者寻找我们组件的反应性属性的变化,并允许我们在变化发生时执行任何逻辑。

首先,我们需要从Vue导入一个辅助器,这与我们导入计算属性的方式相同。

// PasswordScore.vue
export default {
  import { computed, watch } from "vue";
  ...
}

然后,我们在setup 函数中定义我们的观察者。

// PasswordScore.vue
export default {
  import { computed, watch } from "vue";
  ...

  setup(props, { emit }) {
    ...
    watch(isPasswordStrong, (value) => {
      value ? emit("passed") : emit("failed");
    });
    ...
  }
  ...
}

当定义一个观察者时,我们向它传递两个参数。

  1. 我们想要观察的反应式属性
  2. 当该属性的值发生变化时,我们要执行的一个回调。

回调可以访问我们正在观察的属性的当前和以前的值,但在我们的案例中,我们只需要当前的值。

有了当前值在手,我们现在可以在回调中向父级组件发出适当的事件。

emit 方法是从哪里来的?

还有一件事我应该指出:emit 方法来自传递给setup 函数的第二个参数。第二个参数是一个context 对象,它持有一些更有用的元素,但我将留给你自己去深入挖掘它。🙂

所以,我们向我们的父组件发出了事件。现在,我们需要捕捉该事件并在父组件中处理它。

我们已经使用内置的v-on 指令(或@ 简称)来观察任何子组件的事件。让我们转而观察由PasswordScore 组件发出的两个事件。

<!-- PasswordInputForm.vue -->
<template>
  <div class="container mx-auto w-64">
    ...
    <PasswordScore
      :value="password"
      class="mt-2 mb-6"
      @passed="isPasswordStrong = true"
      @failed="isPasswordStrong = false"
    />
    ...
  </div>
</template>

当我们检测到一个适当的事件发生时,我们设置我们的isPasswordStrong 反应式属性的值。这将触发应用于按钮的类的变化,导致按钮相应地被禁用或启用。

<!-- PasswordInputForm.vue -->
<button
  class="mt-6 px-6 py-1 leading-lg select-none bg-blue-400 text-white font-semibold border border-blue-500 rounded"
  :class="[
    {
      'cursor-not-allowed opacity-50 pointer-events-hover': !isPasswordStrong,
    },
  ]"
>
  Submit
</button>

好了,我们现在有一个表单,它检查输入的分数,以文本框信息的形式向用户显示结果,并在密码足够强大时启用按钮。它看起来应该是这样的。

密码强度检查器 - 第3步 - StackBlitz

Vue.js的入门项目

这就足够了,但我们想让它更醒目一些。接下来,我们将添加一个漂亮的进度条,每次分数变化时都会有动画和颜色变化。

第4步:在表单中添加一个彩色编码的、动画的进度条

让我们从我们的进度条的模板开始。我们将把它放在一个单独的组件中,叫做BaseProgressBar

在我们新组件的模板部分,添加两个div 元素。

  1. 一个静态包装器,将显示我们的进度条的边界
  2. 更加动态的封装器,会发生变化。
    1. 它的宽度基于一个width 计算的属性
    2. 它的颜色,基于一个从PasswordScore 组件传递过来的color 属性
 BaseProgressBar.vue 

为了设置进度条的width ,我们使用内联样式绑定,并根据我们当前的分数值分配一个百分比值给我们的div 元素的原始样式width 属性。对于颜色,我们使用HTML类绑定并指定一个Tailwind类,负责改变我们的div 的背景颜色。

script 部分,我们添加了另一个props选项,定义了三个属性。

  1. max:我们的value 属性的最大允许值
  2. value:我们想显示的与max 值有关的当前值
  3. color:用于改变进度条颜色的背景色Tailwind类。

现在,在setup 函数中,我们定义了一个计算属性,负责根据maxvalue 属性计算出当前的条形宽度。

<!-- BaseProgressBar.vue -->
<script>
import { computed } from 'vue';

export default {
  props: {
    max: {
      type: Number,
      required: true,
    },

    value: {
      type: Number,
      required: true,
    },

    color: {
      type: String,
      default: 'bg-transparent',
    },
  },

  setup(props) {
    const width = computed(() => (props.value / props.max) * 100);
    return { width };
  },
};
</script>

就这样,我们的进度条已经准备好了!现在,我们需要在PasswordScore 组件中使用它。
和以前一样,我们导入该组件并注册它。

<!-- PasswordScore.vue -->
<script>
...
import BaseProgressBar from './BaseProgressBar.vue';

export default {
  components: {
    BaseProgressBar,
  },
  ...
}
</script>

最后,我们在模板部分使用该组件。使用属性绑定将属性传递给该组件。

  • value:我们的score
  • max:最大的分数值,我们可以从我们的长度中检索出来。descriptions
  • color:我们当前的描述颜色
<!-- PasswordScore.vue -->
<template>
  <div class="relative select-none">
    <BaseProgressBar
      :value="score"
      :max="descriptions.length"
      :color="description.color"
    />
    <p class="absolute mt-1 text-sm">
      {{ description.label }}
    </p>
  </div>
</template>

密码强度检查器 - StackBlitz

Vue.js的入门项目

然后,我们就可以了。我们的密码分数检查器已经准备好了!

总结

让我们回顾一下我们刚刚完成的工作。我们创建了两个组件:一个是密码检查表单,一个是使用我们的密码和zxcvbn ,以获得该密码的分数。然后,我们实现了基于分数的提交按钮的启用或禁用的逻辑。最后,我们添加了一个进度条,向用户提供关于当前分数的反馈。

咻,我们涵盖了很多内容!希望这能给你们带来一些帮助。希望这能为你的工具箱提供一个新的技巧,以及一个Vue的实际使用案例。