ASP.NET - 使用 jQuery 和 XML 的密码强度指示器






4.88/5 (86投票s)
ASP.NET 密码强度指示器,在功能上类似于 AJAX PasswordStrength 扩展控件,通过使用 jQuery 和 XML 实现。
目录
- 引言
- 入门
- 密码强度指示器插件
- Using the Code
- 后台代码或服务器端代码
- 问答
- 在进度条中显示不同的颜色
- 重复的密码强度指示器问题。
- 重复的密码强度指示器问题。更新 #2 新
- 结论
- 历史
- 资源
引言
上周,我有一个机会帮助在一个使用 ASP 技术开发的遗留 Web 应用程序中实施和集成一个强密码策略。我提出的解决方案是使用 jQuery 来显示密码强度计,以帮助用户创建强密码。然后,我的一位同事问道:“如果客户决定更改密码策略,我们是否必须修改客户端脚本、后台代码和密码策略页面?”答案是“否”,这要归功于 jQuery,客户端脚本和后台代码可以共享同一个 XML 文件。密码策略信息存储在一个 XML 文件中,客户端脚本和后台代码使用 XML 文件中的信息来执行密码强度验证。
我发现了一些不错的 jQuery 插件来显示用户的密码强度,但我更喜欢某种程度上类似于 ASP.NET AJAX PasswordStrength 控件。经过一些研究,我能够找到所有必要的资源并组装出 jQuery 插件来实现目标。下面是该插件的功能列表,我已经准备好了一个演示,欢迎大家下载。
- 以文本和进度条的形式显示密码强度指示器。
- 密码策略、进度条颜色和宽度都存储在 XML 文件中,并由客户端和服务器端使用。
- 使用 XSLT 将密码策略 XML 文件转换为 HTML。
开始
下面是示例应用程序的内容。如果您想测试经典 ASP 代码,请将应用程序部署到您的 IIS Web 服务器。本地 ASP.NET Development Server 不支持经典 ASP。
- Default.aspx – ASP.NET C# 示例代码,包含母版页
- Default.aspx2 – ASP.NET C# 示例代码,不包含母版页
- Password_strength_Indicator.asp – 经典 ASP 示例代码
- jQuery_Password_Strength_Indicator.htm – HTML 示例代码
- PasswordPolicy.xml – 包含密码策略信息
- Password.xslt – 用于将 PasswordPoclicy.xml 文件转换为 HTML 格式
- jQuery.password-strength.js – 这是我创建的插件
- 从 这里 下载最新的 jQuery 库
密码强度指示器插件
在本节中,我将简要介绍 jQuery.password-strength.js 文件中的内容。代码非常直接,并包含注释。有一个 jQuery AJAX 请求用于读取 XML 文件,并根据 XML 文件中的数据填充本地变量。如果您不确定相对 URL,我建议使用指向 XML 文件的绝对 URL。getStrengthInfo()
函数包含检查密码强度并根据用户输入返回相应消息的逻辑。密码强度计条和文本位置相对于Textbox
的位置。
(function($) {
var password_Strength = new function() {
//return count that match the regular expression
this.countRegExp = function(passwordVal, regx) {
var match = passwordVal.match(regx);
return match ? match.length : 0;
}
this.getStrengthInfo = function(passwordVal) {
var len = passwordVal.length;
var pStrength = 0; //password strength
var msg = "", inValidChars = ""; //message
//get special characters from xml file
var allowableSpecilaChars = new RegExp
("[" + password_settings.specialChars + "]", "g")
var nums = this.countRegExp(passwordVal, /\d/g), //numbers
lowers = this.countRegExp(passwordVal, /[a-z]/g),
uppers = this.countRegExp(passwordVal, /[A-Z]/g), //upper case
specials = this.countRegExp(passwordVal,
allowableSpecilaChars), //special characters
spaces = this.countRegExp(passwordVal, /\s/g);
//check for invalid characters
inValidChars = passwordVal.replace(/[a-z]/gi, "") +
inValidChars.replace(/\d/g, "");
inValidChars = inValidChars.replace(/\d/g, "");
inValidChars = inValidChars.replace(allowableSpecilaChars, "");
//check space
if (spaces > 0) {
return "No spaces!";
}
//invalid characters
if (inValidChars !== '') {
return "Invalid character: " + inValidChars;
}
//max length
if (len > password_settings.maxLength) {
return "Password too long!";
}
//GET NUMBER OF CHARACTERS left
if ((specials + uppers + nums + lowers) < password_settings.minLength) {
msg += password_settings.minLength -
(specials + uppers + nums + lowers) + " more characters, ";
}
//at the "at least" at the front
if (specials == 0 || uppers == 0 || nums == 0 || lowers == 0) {
msg += "At least ";
}
//GET NUMBERS
if (nums >= password_settings.numberLength) {
nums = password_settings.numberLength;
}
else {
msg += (password_settings.numberLength - nums) + " more numbers, ";
}
//special characters
if (specials >= password_settings.specialLength) {
specials = password_settings.specialLength
}
else {
msg += (password_settings.specialLength - specials) + " more symbol, ";
}
//upper case letter
if (uppers >= password_settings.upperLength) {
uppers = password_settings.upperLength
}
else {
msg += (password_settings.upperLength - uppers) +
" Upper case characters, ";
}
//strength for length
if ((len - (uppers + specials + nums)) >=
(password_settings.minLength - password_settings.numberLength -
password_settings.specialLength - password_settings.upperLength)) {
pStrength += (password_settings.minLength -
password_settings.numberLength - password_settings.specialLength -
password_settings.upperLength);
}
else {
pStrength += (len - (uppers + specials + nums));
}
//password strength
pStrength += uppers + specials + nums;
//detect missing lower case character
if (lowers === 0) {
if (pStrength > 1) {
pStrength -= 1; //Reduce 1
}
msg += "1 lower case character, ";
}
//strong password
if (pStrength == password_settings.minLength && lowers > 0) {
msg = "Strong password!";
}
return msg + ';' + pStrength;
}
}
//default setting
var password_settings = {
minLength: 12,
maxLength: 25,
specialLength: 1,
upperLength: 1,
numberLength: 1,
barWidth: 200,
barColor: 'Red',
specialChars: '!@#$', //allowable special characters
metRequirement: false,
useMultipleColors: 0
};
//password strength plugin
$.fn.password_strength = function(options) {
//check if password met requirement
this.metReq = function() {
return password_settings.metRequirement;
}
//read password setting from xml file
$.ajax({
type: "GET",
url: "PasswordPolicy.xml", //use absolute link if possible
dataType: "xml",
success: function(xml) {
$(xml).find('Password').each(function() {
var _minLength = $(this).find('minLength').text(),
_maxLength = $(this).find('maxLength').text(),
_numsLength = $(this).find('numsLength').text(),
_upperLength = $(this).find('upperLength').text(),
_specialLength = $(this).find('specialLength').text(),
_barWidth = $(this).find('barWidth').text(),
_barColor = $(this).find('barColor').text(),
_specialChars = $(this).find('specialChars').text(),
_useMultipleColors = $(this).find('useMultipleColors').text();
//set variables
password_settings.minLength = parseInt(_minLength);
password_settings.maxLength = parseInt(_maxLength);
password_settings.specialLength = parseInt(_specialLength);
password_settings.upperLength = parseInt(_upperLength);
password_settings.numberLength = parseInt(_numsLength);
password_settings.barWidth = parseInt(_barWidth);
password_settings.barColor = _barColor;
password_settings.specialChars = _specialChars;
password_settings.useMultipleColors = _useMultipleColors;
});
}
});
return this.each(function() {
//bar position
var barLeftPos = $("[id$='" + this.id + "']").position().left +
$("[id$='" + this.id + "']").width();
var barTopPos = $("[id$='" + this.id + "']").position().top +
$("[id$='" + this.id + "']").height();
//password indicator text container
var container = $('<span></span>')
.css({ position: 'absolute', top: barTopPos - 6,
left: barLeftPos + 15, 'font-size': '75%',
display: 'inline-block', width: password_settings.barWidth + 40 });
//add the container next to textbox
$(this).after(container);
//bar border and indicator div
var passIndi = $('<div id="PasswordStrengthBorder"></div>
<div id="PasswordStrengthBar" class="BarIndicator"></div>')
.css({ position: 'absolute', display: 'none' })
.eq(0).css({ height: 3, top: barTopPos - 16, left: barLeftPos + 15,
'border-style': 'solid', 'border-width': 1, padding: 2 }).end()
.eq(1).css({ height: 5, top: barTopPos - 14, left: barLeftPos + 17 }).end()
//set max length of textbox
//$("[id$='" + this.id + "']").attr('maxLength', password_settings.maxLength);
//add the border and div
container.before(passIndi);
$(this).keyup(function() {
var passwordVal = $(this).val(); //get textbox value
//set met requirement to false
password_settings.metRequirement = false;
if (passwordVal.length > 0) {
var msgNstrength = password_Strength.getStrengthInfo(passwordVal);
var msgNstrength_array = msgNstrength.split(";"), strengthPercent = 0,
barWidth = password_settings.barWidth,
backColor = password_settings.barColor;
//calculate the bar indicator length
if (msgNstrength_array.length > 1) {
strengthPercent = (msgNstrength_array[1] /
password_settings.minLength) * barWidth;
}
$("[id$='PasswordStrengthBorder']").css
({ display: 'inline', width: barWidth });
//use multiple colors
if (password_settings.useMultipleColors === "1") {
//first 33% is red
if (parseInt(strengthPercent) >= 0 &&
parseInt(strengthPercent) <= (barWidth * .33)) {
backColor = "red";
}
//33% to 66% is blue
else if (parseInt(strengthPercent) >= (barWidth * .33) &&
parseInt(strengthPercent) <= (barWidth * .67)) {
backColor = "blue";
}
else {
backColor = password_settings.barColor;
}
}
$("[id$='PasswordStrengthBar']").css({ display: 'inline',
width: strengthPercent, 'background-color': backColor });
//remove last "," character
if (msgNstrength_array[0].lastIndexOf(",") !== -1) {
container.text(msgNstrength_array[0].substring
(0, msgNstrength_array[0].length - 2));
}
else {
container.text(msgNstrength_array[0]);
}
if (strengthPercent == barWidth) {
password_settings.metRequirement = true;
}
}
else {
container.text('');
$("[id$='PasswordStrengthBorder']").css("display", "none"); //hide
$("[id$='PasswordStrengthBar']").css("display", "none"); //hide
}
});
});
};
})(jQuery);
使用代码
将Textbox
控件、jQuery 库和插件包含到网页中。将txtPassword ID 更改为您想要的 ID。使用此代码行“var myPlugin = $("[id$='txtPassword']").password_strength();
”来调用插件。要检查密码是否符合密码策略,请调用metReq()
函数,使用此代码行“myPlugin.metReq()
”。有关详细信息,请参见列表 2。jQuery$("[id$='txtPassword']")
选择器将与 ASP.NET 服务器控件一起使用,所以不用担心使用我的“txtPassword.ClientID
”。条的颜色、宽度和密码策略信息可以通过 XML 文件进行修改。
<div style="height:400px"><br />
<asp:label runat="server" id="lblPassword"
AssociatedControlId="txtPassword">Enter Password:</asp:label>
<asp:TextBox ID="txtPassword" runat="server"></asp:TextBox><br />
<a id="passwordPolicy" href="#">Password policy
<asp:Button ID="btnSubmit" runat="server" Text="Submit" />
<br /><br />
<asp:Label ID="ResultLabel" runat="server" Text=""></asp:Label>
</div>
<script src="Script/jquery-1.4.4.min.js" type="text/javascript"></script>
<script src="Script/jquery.password-strength.js" type="text/javascript"></script>
<script type="text/javascript">
$(document).ready(function() {
var myPlugin = $("[id$='txtPassword']").password_strength();
$("[id$='btnSubmit']").click(function() {
return myPlugin.metReq(); //return true or false
});
$("[id$='passwordPolicy']").click(function(event) {
var width = 350, height = 300, left = (screen.width / 2) - (width / 2),
top = (screen.height / 2) - (height / 2);
window.open("PasswordPolicy.xml", 'Password_poplicy',
'width=' + width + ',height=' + height + ',left=' + left + ',top=' + top);
event.preventDefault();
return false;
});
});
</script>
我们可以使用 XLST 来显示 PasswordPolicy.xml 的内容,或者编写代码来提取其内容。我倾向于使用 XLST 以避免编写额外的代码。下面是密码策略页面。如果您想了解更多关于如何使用 XSLT 显示 XML 的信息,请点击 这里。
后台代码或服务器端代码
我们使用的正则表达式列在列表 3 中。数字是可调的,并且来自 XML 文件。
(?=^.{12,25}$)(?=(?:.*?\d){2})(?=.*[a-z])(?=(?:.*?[A-Z]){2})
(?=(?:.*?[!@#$%*()_+^&}{:;?.]){2})(?!.*\s)[0-9a-zA-Z!@#$%*()_+^&]*$
列表 4 显示的代码用于动态生成正则表达式。因此,明天如果您的客户告诉您增加密码策略中所需的数字,您就不必搜索或创建新的正则表达式。您所要做的就是更改 PasswordPolicy.xml 文件中的设置。您可以在 这里 验证正则表达式。
void btnSubmit_Click(object sender, EventArgs e)
{
PasswordSetting passwordSetting = Helper.GetPasswordSetting();
StringBuilder sbPasswordRegx = new StringBuilder(string.Empty);
//min and max
sbPasswordRegx.Append(@"(?=^.{" + passwordSetting.MinLength + "," +
passwordSetting.MaxLength + "}$)");
//numbers length
sbPasswordRegx.Append(@"(?=(?:.*?\d){" + passwordSetting.NumsLength + "})");
//a-z characters
sbPasswordRegx.Append(@"(?=.*[a-z])");
//A-Z length
sbPasswordRegx.Append(@"(?=(?:.*?[A-Z]){" + passwordSetting.UpperLength + "})");
//special characters length
sbPasswordRegx.Append(@"(?=(?:.*?[" + passwordSetting.SpecialChars + "])
{" + passwordSetting.SpecialLength + "})");
//(?!.*\s) - no spaces
//[0-9a-zA-Z!@#$%*()_+^&] -- valid characters
sbPasswordRegx.Append(@"(?!.*\s)[0-9a-zA-Z" +
passwordSetting.SpecialChars + "]*$");
if (Regex.IsMatch(txtPassword.Text, sbPasswordRegx.ToString()))
{
ResultLabel.Text = "Password confront password policy!";
}
else
{
ResultLabel.Text = "Password does not confront password policy!";
}
}
问答
为什么进度条指示器看起来与演示应用程序中的不同?
确保在<html>
标签之前声明了正确的DocType
。点击此处了解更多关于它的信息。
如何将 TextBox 更改为密码模式?
对于 ASP.NET 控件,将TextMode
属性设置为“Password
”。对于 HTML 控件,将 type 属性设置为“Password
”。
为什么在 ASP.NET Development Server 上运行经典 ASP 代码时会收到“此类型的页面未提供”的错误?
将演示应用程序部署到 IIS Web 应用程序服务器。
我可以使用这个插件与其他编程语言一起使用吗?
是的。
在进度条中显示不同的颜色
我收到了读者关于在进度条中使用不同颜色的几个建议。我尝试在对现有代码进行最小更改的情况下实现这一点。目前的实现没有复杂的算法来为字符分配不同的权重。以下是我实现它的方法,请参阅列表 5 以获取完整详细信息。
- 颜色基于密码长度。
- 如果密码长度在 0% 到 33% 之间,则显示红色。
- 如果密码长度在 33% 到 67% 之间,则显示蓝色。
- 如果超过 67%,则显示 PasswordPolicy.xml 文件中指定的颜色。
我还向 PasswordPolixy.xml 添加了useMultipleColors
属性,以便用户可以选择启用或禁用多种颜色。
//use multiple colors
if (password_settings.useMultipleColors === "1") {
//first 33% is red
if (parseInt(strengthPercent) >= 0 &&
parseInt(strengthPercent) <= (barWidth * .33)) {
backColor = "red";
}
//33% to 66% is blue
else if (parseInt(strengthPercent) >= (barWidth * .33) &&
parseInt(strengthPercent) <= (barWidth * .67)) {
backColor = "blue";
}
else {
backColor = password_settings.barColor;
}
}
重复的密码强度指示器问题
最近,一位用户向我报告了“使用 jQuery 和 XML 实现的密码强度指示器”插件的问题。该问题会发生(图 3.1),如果 Label 控件的 ID 包含 Textbox 控件的 ID(列表 3.1)。
<asp:label runat="server" id="lblPassword" AssociatedControlId="Password">Enter Password:</asp:label>
<asp:TextBox ID="Password" runat="server"></asp:TextBox>
解决方案:此问题非常罕见,但有几种解决方案可以解决它。
- 从 Label 控件中排除 id 属性
- 确保 Label 控件 ID 不包含 Textbox/input 控件的 ID
- 修改此行
var myPlugin = $("[id$='txtPassword']").password_strength();
to
var myPlugin = $("input[id$='txtPassword']").password_strength();
重复的密码强度指示器问题:更新 #2
几周前,一位 CodeProject 会员报告说,“使用 jQuery 和 XML 实现的密码强度指示器插件”显示了重复的指示器。昨天,我尝试将插件集成到 MVC 3 应用程序中,并遇到了提到的问题。
经过一番研究,我注意到 jQuery ID 选择器有问题。我应该使用属性等于选择器 [id="value"] 而不是属性结尾选择器 [id$="value"]。前者选择具有特定属性且其值与给定值完全相等的元素。后者选择具有特定属性且其值以给定字符串结尾的元素。这就解释了为什么条形指示器同时出现在密码和确认密码文本框旁边。我已经更新了插件,这是使用插件的正确方法。
<script type="text/javascript">
$(document).ready(function () {
var myPlugin = $("input[id='Password']").password_strength();
$("[id='submit']").click(function () {
return myPlugin.metReq(); //return true or false
});
$("[id='passwordPolicy']").click(function (event) {
var width = 350, height = 300, left = (screen.width / 2) - (width / 2),
top = (screen.height / 2) - (height / 2);
window.open("PasswordPolicy.xml", 'Password_poplicy',
'width=' + width + ',height=' + height + ',left=' + left + ',top=' + top);
event.preventDefault();
return false;
});
});
</script>
结论
我希望有人能发现这些信息很有用,并使您的编程工作更轻松。如果您发现任何错误或不同意本文内容,或想帮助改进本文,请给我发消息,我会与您一起修改。我建议下载演示并进行探索,以便完全掌握其概念,因为我可能会遗漏本文中的一些重要信息。如果您想帮助改进本文,请给我发电子邮件。
历史
- 2011/09/01 - 首次发布 (v01.00.00)
- 2011/01/13 - v01.01.00 发布
- 将文本容器的宽度设置为进度条宽度 + 40 像素
- 正确显示缺失的小写字符
- 包含插件的源代码
- 2011/01/20 - v01.02.00 发布
- 在 PasswordPolicy.xml 文件中添加了
useMultipleColors
属性。1=是,0=否 - 在插件中添加了新的逻辑,当强度在 0% 到 33% 之间时显示红色背景。当强度在 33% 到 67% 之间时,背景色为蓝色。
- 2012/07/23 - v01.03.00 发布
- 将属性结尾选择器 [id$="value"] 替换为属性等于选择器 [id="value"]
- https://api.jqueryjs.cn/category/selectors/