提供即时反馈是现在的事情。为什么要限制自己检查用户名和电子邮件地址?为什么不扩展它以提供有关用户输入的密码强度的快速视觉反馈?今天,我们将看看如何使用正则表达式和简单的算法创建一个简单的密码强度检查器。
正如大多数安全专家会告诉您的那样,用户始终是最薄弱的环节。当用户选择极其不明智的密码时,即使是最安全的系统也会受到攻击。考虑到这一点,最近的趋势似乎是向用户提供有关密码强度的快速反馈,以便用户可以扩展或修改密码以使其更安全。
今天,我们将使用 jquery 库、一堆正则表达式和一个非常简单的算法来创建一个基本的密码强度检查器。感兴趣的?让我们马上开始吧!这是我们今天要构建的演示:
See the Pen Simple Password Strength Checker by Envato Tuts+ (@tutsplus) on CodePen.
设计目标
我们针对此特定功能的设计目标相对较小。
向用户提供有关密码强度的视觉反馈。
反馈必须是即时的。这意味着无需单击按钮即可测试强度。
触发事件可以是任何键盘事件。我之所以选择keyup它,是因为它最适合我们的特定需求。
对于视觉反馈,单独修改文本虽然有用,但严重缺乏。我还选择更改背景颜色以引起用户的注意。
提供额外的可量化反馈,以便用户知道密码在哪些部门缺乏强度以及如何改进。
现在我们已经充分了解了我们的需求,我们可以进入下一阶段。
行动计划
我们现在将决定需要完成的各个步骤的顺序。
将事件处理程序挂接到keyup输入框的事件。
让事件处理程序检查输入,但将其他一切委托给各个辅助方法。
辅助方法应该负责解析输入并对其进行分析,计算复杂度并打印出结果。
确保事件处理程序仅在输入的长度大于预期的最小值时才触发辅助方法,以免在无效条目上浪费 CPU 周期。
将控制权返回给事件处理程序,以防需要执行任何其他操作。
算法
为了使这篇文章简洁易懂,我决定使用一个非常基本的算法。该算法分析字符串,为额外长度和使用数字、符号和大写字母提供奖励,并为仅输入字母或数字的输入提供惩罚。我们不会考虑根据字典检查输入,因为这超出了本文的范围。
首先,我们检查输入字符串的长度。如果它大于最小长度,则给它一个 50 的基本分数。否则将其设为 0。接下来,遍历字符串的每个字符并检查它是符号、数字还是大写字母。如果是这样,请记下它。
然后检查字符串有多少额外字符超过建议的最小值,并为每个字符授予奖励。如果字符串包含大写字母、数字和符号的组合,或全部包含这三者,则也给予奖励。为他们每个人的存在授予奖金。
检查字符串是否只包含小写字母或数字,如果是,则进行惩罚。
将所有数字相加并相应地决定密码的强度。
这是算法的长短。它不会非常复杂,但会捕获很多错误密码。一旦我们在代码中看到它,您就会更好地理解这一点。
核心标记
演示页面的 html 标记如下所示:
<div id="container"> <h1>A simple password strength checker</h1> <p>Type in your password to get visual feedback regarding the strength of your password.</p> <p>I assure you, I am not stealing your passwords. The form doesn't not submit. You can look through the source if you are suspicious. :)</p> <div class="block"> <input id="password" /> <div id="complexity" class="default">Enter a random value</div> </div> <div class="block"> <div id="results" class="default">Breakdown of points</div> <div id="details"></div> <p class="message"></p> </div> </div>
忽略所有常见的标记。请注意带有 id 的 input 元素,带有 id的元素显示密码的复杂性,带有 id 的元素显示点的细分。 passworddivcomplexitydivdetails
css 样式
input { width: 400px; margin: 2rem 0 0.1rem 0; padding: 0.2rem; font-size: 1.5rem; font-family: "Lato"; outline: none; border: none; border-bottom: 2px solid black; } #container { width: 800px; margin: 0 auto; padding: 50px 0 0 0; } .block { width: 400px; margin: 0 auto; } #complexity, #results { width: 400px; padding: 3px 0; height: 20px; color: #000; font-size: 1.25rem; text-align: center; color: white; font-weight: bold; } div#complexity { width: fit-content; padding: 0.1rem 0.5rem; font-size: 1rem; text-transform: uppercase; } #results { margin: 30px 0 20px 0; }
上面的代码片段只是用于布局和排版的样板 CSS。您会注意到我们已将密码复杂性的宽度设置div为fit-content。这使我们可以将其保持在适合内容所需的宽度。
我们还需要一些额外的 CSS 来区分不同的密码强度。带有类的段落message将包含有关惩罚的消息,因此我们将其设为粗体和红色。
.default { background-color: black; } .weak { background-color: #C62828; } .strong { background-color: #FF8F00; } .stronger { background-color: #1976D2; } .strongest { background-color: #388E3C; } span.value { font-weight: bold; float: right; } p.message { color: red; font-weight: bold; }
javascript 实现
现在我们有了一个可靠的框架和一些基本的样式,我们可以开始编写所需的功能。请注意,我们广泛使用了 jQuery。如有必要,请随时链接到 Google 的 CDN。
变量和事件处理
由于要进行大量的数字杂耍,我们需要一堆变量来保存这些值。由于这是一个演示而不是生产代码,我决定将变量声明为全局变量并通过辅助方法访问它们,而不是在内部声明它们然后将它们传递给函数。
let baseScore = 0; let score = 0; const minPasswordLength = 8; const complexity = document.queryselector("#complexity"); const passwordInput = document.querySelector("#password"); let num = { excess: 0, upper: 0, numbers: 0, symbols: 0 }; let bonus = { excess: 3, upper: 4, numbers: 5, symbols: 5, combo: 0, onlyLower: 0, onlyNumber: 0, uniqueChars: 0, repetition: 0 };
我们有三个常量。第一个称为minPasswordLength,确定我们开始指定密码强度的最小密码长度。另外两个常量存储对input元素的引用和div将包含强度指示器的 。
我们创建两个对象来存储用于计算的数字。第一个称为num,包含额外字符、大写字符、数字和符号的数量。我们对第二个对象做同样的事情,叫做bonus。该num对象包含字符数,而该bonus对象包含奖金乘数和惩罚值。您可以只创建单个变量,但我认为这看起来更简洁。
不要忘记将事件处理程序连接到事件。
passwordInput.addeventListener("keyup", checkVal);
只要我们的元素上有事件,checkVal就会调用该函数。keyuppasswordInput
事件处理器
让我们从编写事件处理程序回调函数的代码开始。我们首先获取用户输入的密码,然后在init()函数内做一些变量初始化,将很多变量的值重置为0。
function checkVal() { let strPassword = passwordInput.value; init(); if (strPassword.length >= minPasswordLength) { baseScore = 50; analyzeString(strPassword); calcComplexity(); } else { baseScore = 0; } outputResult(strPassword); } function init() { num.excess = 0; num.upper = 0; num.numbers = 0; num.symbols = 0; bonus.combo = 0; bonus.onlyLower = 0; bonus.onlyNumber = 0; bonus.uniqueChars = 0; bonus.repetition = 0; baseScore = 0; score = 0; }
接下来,我们检查输入字符串的长度。如果它大于或等于最小指定长度,我们可以继续。我们将基本分数设置为 50,并调用辅助函数来分析字符串并计算其复杂性。
如果它小于预期的长度,我们就将基本分数设置为 0。
然后我们调用outputResult()函数,它负责理解计算的计算。我们稍后会看到它是如何工作的。
分析输入
我们现在将定义analyzeString()函数来找出密码中的字符类型并相应地分配奖励和惩罚。这可能看起来有点复杂,但我向你保证,这只是因为正则表达式。让我们逐个回顾代码。
function analyzeString(strPassword) { let charPassword = strPassword.split(""); for (i = 0; i < charPassword.length; i++) { if (charPassword[i].match(/[A-Z]/g)) { num.upper++; } if (charPassword[i].match(/[0-9]/g)) { num.numbers++; } if (charPassword[i].match(/(.*[!,@,#,$,%,^,&,*,?,_,~])/g)) { num.symbols++; } } num.excess = charPassword.length - minPasswordLength; if (num.upper && num.numbers && num.symbols) { bonus.combo = 25; } else if ( (num.upper && num.numbers) || (num.upper && num.symbols) || (num.numbers && num.symbols) ) { bonus.combo = 15; } if (strPassword.match(/^[\sa-z]+$/)) { bonus.onlyLower = -15; } if (strPassword.match(/^[\s0-9]+$/)) { bonus.onlyNumber = -35; } }
首先,我们需要弄清楚所讨论字符串的组成。所以我们需要弄清楚字符串是否包含大写字母、数字或符号,如果是,则存在多少个。考虑到这一点,我们遍历字符数组并检查每个字符以查看其类型。该match()方法让我们将字符串与正则表达式进行匹配
接下来,我们必须确定输入字符串的长度与密码的指定最小长度之间的差异。这给了我们更多的角色来玩。
然后我们检查字符串是否包含大写字母、数字和符号。如果是这样,请给予奖金。我们还会检查它是否具有其中两个的组合,如果是,则给予较小的奖励。
最后,我们检查字符串是否扁平:它是否仅包含小写字母或仅包含数字。我们用正则表达式检查这一点,如果是,则对这种做法的密码进行处罚。
计算复杂度
计算复杂度相对容易,因为我们只需对之前分配给变量的值进行简单的加法和乘法运算。我们将基本分数添加到多余字符数及其乘数的乘积中。我们对大写字母、数字和符号执行相同的操作。然后我们为组合添加奖励(如果存在),如果字符串是扁平的则添加惩罚。
function calcComplexity() { score = baseScore + num.excess * bonus.excess + num.upper * bonus.upper + num.numbers * bonus.numbers + num.symbols * bonus.symbols + bonus.combo + bonus.onlyLower + bonus.onlyNumber; }
更新用户界面
现在所有计算都已完成,我们可以更新 UI 以反映更改。这是每个州。
我们将在这里定义两个函数。调用的main函数outputResult()决定了我们里面的文本div,要删除的类,要添加的类。调用的辅助函数updateComplexity()实际上进行了更新。
function updateComplexity(message, removeClasses, addClass) { complexity.innerHTML = message; complexity.classList.remove(...removeClasses); complexity.classList.add(addClass); } function outputResult(strPassword) { let removeClasses = ["weak", "strong", "stronger", "strongest"]; if (passwordInput.value == "") { updateComplexity("Enter a random value", removeClasses, "default"); } else if (strPassword.length < minPasswordLength) { updateComplexity( `At least ${minPasswordLength} characters please!`, removeClasses, "weak" ); } else if (score < 50) { updateComplexity("Weak!", removeClasses, "weak"); } else if (score >= 50 && score < 75) { updateComplexity("Average!", removeClasses, "strong"); } else if (score >= 75 && score < 100) { updateComplexity("Strong!", removeClasses, "stronger"); } else if (score >= 100) { updateComplexity("Secure!", removeClasses, "strongest"); } }
这里没什么特别的,但我们将逐行介绍它。
我们首先检查输入是否为空。如果是这样,请更改结果的文本并添加一个default类以将其背景颜色更改回其原始黑色。
如果它小于指定的最小长度,我们相应地更改文本并添加一个weak类,使其背景为红色。如果总分小于 50,我们也这样做,但将文本更改为weak。
随着分数的增加,我们相应地更改文本并添加必要的类。随意更改每个评级的基线分数。我只是输入了不科学的值来让演示继续进行。
更新详细分类
随着主要结果的更新,我们现在可以看看更新统计数据。
function outputResult(strPassword) { // Previous Code document.querySelector("#details").innerHTML = `Base Score :<span class="value">${baseScore}</span><br /> Length Bonus :<span class="value">${num.excess * bonus.excess} [${num.excess} x ${bonus.excess}]</span><br /> Upper case bonus :<span class="value">${num.upper * bonus.upper} [${num.upper} x ${bonus.upper}]</span><br /> Number Bonus :<span class="value">${num.numbers * bonus.numbers} [${num.numbers} x ${bonus.numbers}]</span><br /> Symbol Bonus :<span class="value">${num.symbols * bonus.symbols} [${num.symbols} x ${bonus.symbols}]</span><br /> Combination Bonus :<span class="value">${bonus.combo}</span><br /> Lower case only penalty :<span class="value">${bonus.onlyLower}</span><br /> Numbers only penalty :<span class="value">${bonus.onlyNumber}</span><br /> Repeating pattern penalty :<span class="value">${bonus.repetition}</span><br /> Total Score:<span class="value">${score}</span><br />`; }
这部分并不像看起来那么混乱。让我解释。
我没有更新详细结果的各个值,而是只更新容器的完整 HTML 值。我知道当这些盒子的数量加起来时它会变慢,但是单独访问每个元素然后更新它的值以进行小型演示似乎适得其反。所以跟我一起跑吧。
这就像将常规 HTML 注入到一个元素中,只是我们在其中放置了几个变量以使细节能够即时更新。每个值都有一个value类以使其变为粗体。我们还显示特殊字符的数量及其乘数,以便用户可以衡量哪些元素获得更多权重。
要记住的要点
该init()函数不仅仅对初始值赋值有用。它实际上负责在每次keyup事件后重置分数和惩罚值。为什么这很重要?重置很重要,因为它可以确保密码分数和分数细分是最新的。
假设有人在密码中键入了一个符号。这可能会导致他们获得一些奖励积分。如果每次击键后没有重置,即使您稍后删除符号字符,奖励积分也会保留在系统中。
您还应该注意我们如何在附加新类之前从密码强度指示器中删除所有类。出于同样的原因,这也很重要。如果有人输入强密码,我们将添加指示强密码的类。但是,如果在用较弱的密码替换强密码时未删除该类,则密码强度指示器将停留在强密码并产生误导。
我们的密码强度检查器的局限性
要了解我们在这里构建的密码强度检查器的局限性,让我们快速回顾一下我们是如何计算密码强度的。
只使用小写字母会受到惩罚,但也有长度奖励。小写惩罚也保持在 -15。另一方面,超过密码中最低 8 个字符的长度会有一个长度奖励。这意味着惩罚将通过仅使用 5 个额外的小写字母来抵消。
通过使用大写字母,我们可以更快地获得评级高的密码。这样做不仅可以避免小写字母的惩罚,还可以通过使用大写字母获得奖励。
您可能还注意到该算法目前不考虑字符重复。这可能导致相对较弱的密码被误认为是强密码。下图就是一个很好的例子:
我想我们都同意,字母a或A的重复序列不是强密码。我们现在将进行一些额外的检查,以更好地估计密码强度。在函数底部添加以下代码analyzeString(),您就可以开始了。
function analyzeString(strPassword) { // Previous Code let lcPassword = strPassword.toLowerCase(); let uniqueChars = new Set(lcPassword).size; if (uniqueChars <= 3) { bonus.uniqueChars = -Number.MAX_VALUE; document.querySelector("p.message").innerHTML = "Too Few Unique Characters."; } else if (uniqueChars >= 3 && uniqueChars < 6) { bonus.uniqueChars = -5 * (36 - uniqueChars * uniqueChars); } else { bonus.uniqueChars = 0; document.querySelector("p.message").innerHTML = ""; } }
让我们看看这里发生了什么。我们首先将整个密码转换为小写字母。之后,我们将其转换为 aSet并使用其size属性来获取唯一字符的数量。如果唯一字符数小于或等于 3,我们将uniqueChars奖金的值设置为一个较大的负值。如果至少有 6 个不同的字符,我们将取消对重复字符的惩罚。
请注意上图中使用的第二个密码——它使用了许多独特的字符,但仍有一些重复。我们可以在analyzeString()函数中再包含一项检查以检查重复模式。这将依赖正则表达式并检查密码中是否重复了 3 个或更多字母数字字符的序列。
我们将在analyzeString()函数底部添加以下代码。
function analyzeString(strPassword) { // Previous Code if (checkRepetition(strPassword)) { bonus.repetition = -50; } else { bonus.repetition = 0; } }
这是我们checkRepetition()函数的定义:
function checkRepetition(strPassword) { return /([a-z0-9]{3,})\1/.test(strPassword); }
我们只是test()在输入密码上使用该方法,只要出现三个或更多字符的重复模式,它就会返回 true。
在上图中,您可以看到我们已经成功解决了重复字符序列的问题。然而,我已经给出了密码强度检查器的另一个限制的例子。
您可以看到Pa$$W0rd$显示为安全密码,而实际上它很快就会被破解。这是由于我们这里的算法很简单。我们不会为此检查字符替换或通用密码或模式。做这样的事情会增加本教程的难度,同时降低它的可访问性,这两者都是我不希望在这篇特定的文章中出现的。
根据字典查找输入确实超出了本文的范围,并且需要将大量字典下载到客户端或将其连接到服务器端系统才能执行此操作。再一次,这次我真的很想避开他们两个。
结论
你已经知道了:如何添加一个用户友好的功能,让用户知道他们刚刚输入的密码强度的能力,到你的项目中。希望您发现本教程很有趣,并且对您有用。随意在您项目的其他地方重用此代码!
发表评论