| |
|
系统登录的设计与研究 |
|
张德强 |
摘要:身份识别认证是保障系统安全的第一道防线,也是最重要的一道。本文通过讲解密码加密技术和验证码技术,构建WebService登录接口实现统一的系统登录认证平台,并通过引入硬件设备实现双因子认证。以实例的方式探讨了系统登录的常规设计和通用方法。
关键词:系统安全,密码加密,验证码,Web Service
一、引言
随着信息化的不断推广,各行业对企事业单位内部、企业间信息化的重视程度也不断提高,重视程度的提高大大促进了各行业无纸化办公的形成。企事业单位流程化管理的规范,节省了大量的人力、物力、资金等资源。信息系统已成为现代企业、政府机构等社会组织提高自身素质和实现组织目标的战略措施。
在信息化发展过程中,人们对信息化中数据信息安全越来越担忧。系统的登录部分对于系统安全而言就象打开保险库的钥匙。把握好这把钥匙可以有效的防止非法用户访问,确保系统安全。系统登录技术涉及密码加密、验证码技术和采用硬件实现算因子认证等技术。
密码加密可以避免密码在传输过程中被侦测窃听,同时可以防止系统管理人员通过数据库知晓所有人员密码,绕开了登录系统。验证码技术现在应用已经非常广泛,比如各大网站系统登录、用户注册、未登录用户评论的发表等都会应用验证码。其原理是根据一定的随机数产生算法生成一串字符串,加入背景、前景及扭曲干扰最终生成验证图片。该图片只能通过肉眼才能识别出其中的验证码字符信息。有效的防止如利用机器人自动批量注册、防止自动批量的提交评论文章以及防止对特定用户利用程序进行暴力破解。系统登录中传统的用户认证方式通常采用人工输入“用户名+密码”的方式。而“用户名+密码”的方式被证明存在众多问题:密码容易被剽窃,用户设置密码通常比较简单,而密码易于共享的特点则可能使一切安全设置流于形式。采用硬件USB
Key的身份认证,可以弥补“用户名+密码”认证方式的种种缺陷和不足,消除由于网络内部用户有意或无意造成的安全隐患。
二、密码加密技术
加密技术通常分为两大类:“对称式(symmetric)”和“非对称式(asymmetric)”。对称式加密就是加密和解密数据时使用相同的密钥和初始化矢量,典型的有DES、
TripleDES和Rijndael算法等。它适用于不需要传递密钥的情况,主要用于本地文档或数据的加密。不对称算法有两个不同的密钥,称为“公钥”和“私钥”,它们两个必需配对使用。公共密钥在网络中传递,用于加密数据,而私有密钥用于解密数据。不对称算法主要有RSA、DSA等,主要用于网络数据的加密。
.Net中常见的加密和编码算法都已经集成在.NET
Framework中,实现这些算法的名称空间是:System.Security.Cryptography。由于随着整个框架组件一起共享,密码服务更容易实现了,仅仅需要掌握System.Security.Cryptography名字空间的功能和用于解决特定方案的类。
1. 对称加密
在实际运用中常使用DES和TripleDES对称加密算法。DES主要采用替换和移位的方法,用56位密钥对64位二进制数据块进行加密。TripleDES是在DES的基础上采用三重DES,即用两个56位的密钥K1、K2,发送方用K1加密,K2解密,再使用K1加密。接收方使用K1解密,K2加密,再使用K1解密,其效果相当于密钥长度加倍。DES和TripleDES在.Net中的实现是通过DESCryptoServiceProvider和TripleDESCryptoServiceProvider加密类来实现的。
对称算法是在数据流通过时对它进行加密。因此首先需要建立一个正常的流(例如I/O流)。加密或解密的字符串读入流,算法实例提供一个对象来执行实际数据处理。以DES加密算法为例看看具体实现过程,具体代码如下:
/// 根据钥匙加密字符串,返回加密后的字符串
/// <param name="stringToEncrypt">要加密的字符串</param>
/// <param name="sEncryptionKey">加密的钥匙</param>
public static string Encrypt( string stringToEncrypt, string
sEncryptionKey)
{
byte[] key = {};
byte[] IV = {10, 20, 30, 40, 50, 60, 70, 80};
byte[] inputByteArray;
try
{
key = Encoding.UTF8.GetBytes(sEncryptionKey.Substring(0,8));
//实例化DES加密类
DESCryptoServiceProvider des = new DESCryptoServiceProvider();
inputByteArray = Encoding.UTF8.GetBytes(stringToEncrypt);
MemoryStream ms = new MemoryStream();
CryptoStream cs = new CryptoStream(ms, des.CreateEncryptor(key, IV),
CryptoStreamMode.Write);
cs.Write(inputByteArray, 0, inputByteArray.Length);
cs.FlushFinalBlock();
return Convert.ToBase64String(ms.ToArray());
}
catch
{ return (string.Empty);}
}
Encrypt加密函数通过指定加密密钥Key和初始化向量(IV)的DESCryptoServiceProvider,传人加密字符串stringToEncrypt,将加密后的字符串作为函数结果返回。如果加密失败返回空字符。解密过程是加密过程的逆过程,方法相同,把CreateEncryptor改为
CreateDecryptor。注意解密过程必须使用加密时所用的同一Key和IV进行解密。解密代码如下:
///用钥匙解密加密的字符串,如果解密失败,返回空字符串
///<param name="stringToDecrypt">要解密的字符串</param>
///<param name="sEncryptionKey">解密所用的钥匙</param>
public static string Decrypt( string stringToDecrypt, string
sEncryptionKey)
{
byte[] key = {};
byte[] IV = {10, 20, 30, 40, 50, 60, 70, 80};
byte[] inputByteArray = new byte[stringToDecrypt.Length];
try
{
key = Encoding.UTF8.GetBytes(sEncryptionKey.Substring(0,8));
DESCryptoServiceProvider des = new DESCryptoServiceProvider();
inputByteArray = Convert.FromBase64String(stringToDecrypt);
MemoryStream ms = new MemoryStream();
CryptoStream cs = new CryptoStream(ms, des.CreateDecryptor(key, IV),
CryptoStreamMode.Write);
cs.Write(inputByteArray, 0, inputByteArray.Length);
cs.FlushFinalBlock();
Encoding encoding = Encoding.UTF8 ;
return encoding.GetString(ms.ToArray());
}
catch
{ return (string.Empty);}
}
每种算法都有CreateEncryptor和CreateDecryptor两个方法,它们返回实现ICryptoTransform接口的对象。根据以上代码很容易改为TripleDES或者Rijndael算法。
2. 非对称加密
非对称算法主要有RSA、DSA等。通常用于网络数据的加密,信息接收者通过ToXmlString生成公钥和私有,通过网络或者其它方式把公钥传递给信息发送者。信息发送者通过FromXmlString导入公钥,并对字符流进行加密。接收方接收到数据后用自己的私钥解密数据。这两种加密算法分别通过RSACryptoServiceProvider和DSACryptoServiceProvider类来实现。以RSA为例,具体代码如下:
//待加密的明文
string originText="I am Dean Zhang";
RSACryptoServiceProvider rsaReceive =new RSACryptoServiceProvider();
RSACryptoServiceProvider rsaSend = new RSACryptoServiceProvider();
//接收方先生成公钥,并将此公钥公开给信息发送者,参数false 表示只生成公钥, 如果为true, 则生成私钥
string publicKey = rsaReceive.ToXmlString(false);
// 产生私钥
string privatekey = rsaReceive.ToXmlString(true);
//发送方接收公钥, 并用此公钥加密数据
rsaSend.FromXmlString(publicKey);
//发送方执行加密程序,Encrypt第二个参数指示是否使用OAEP,如果为 true,则使用 OAEP 填充,如果为
false,则使用 PKCS#1 1.5版填充, 解密时必须跟加密时的选择相同
byte[] cryp =
rsaSend.Encrypt(Encoding.UTF8.GetBytes(originText),false);
//接收方用自己的私钥解密
byte[] b_OriginText = rsaReceive.Decrypt(cryp, false);
3. 哈希(Hash)值
哈希算法将任意长度的二进制值映射为固定长度的较小二进制值,这个小的二进制值称为哈希值。哈希值是一段数据唯一且极其紧凑的数值表示形式。如果散列一段明文而且哪怕只更改该段落的一个字母,随后的哈希都将产生不同的值。要找到散列为同一个值的两个不同的输入,在计算上是不可能的,所以数据的哈希值可以检验数据的完整性。在实际应用中常见的就是MD5加密算法,密码通过MD5算法转换为固定长度的Hash值并存储。验证过程为把用户输入的密码经过MD5算法和存储的HASH进行比较,如果一致表明通过。MD5算法示例代码如下:
public static string getMD5Str(string ConvertString)
{
string md5Str = "";
MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider();
string md5Str =
BitConverter.ToString(md5.ComputeHash(UTF8Encoding.Default.GetBytes(ConvertString)),
4, 8);
md5Str = md5Str.Replace("-", "");
return md5Str;
}
其实严格的来说,这种方式不是一种对数据进行加密的算法,是一种签名算法。他不能对加密的密码进行解密,但因为不同的密码不可能有相同的Hash值,可以通过这个特性对密码进行验证,其缺点是不能还原(或者说是解密)读取用户密码信息。
三、验证码技术
验证码的实现流程为:服务器端采用随机数产生算法生成验证码字符串,保存在服务器端(如内存中)并写入图片,图片里加上一些干扰象素(防止OCR),把该图片信息发送给客户端(可能是浏览器或者C/S客户端),客户端输入验证码图片上字符信息,然后提交给服务器端,提交的字符和服务器端保存的该字符进行比较。一致就继续,否则返回提示。攻击者编写的robot程序,很难识别验证码字符,顺利的完成自动注册,登录,批量提交等,而用户可以识别填写,所以这就实现了阻挡攻击的作用。防止图片的字符被自动识别,就要看图片上的干扰强度了。
普通的验证码字符大多是“随机数字+随机英文字母”,并且只是增加了背景和前景噪音干扰。验证作用弱,很多程序可以通过OCR识别出验证图片的验证码信息。通过随机生成汉字字符串,把生成的图像进行波形扭曲,有效的防止OCR,增强抗干扰。
首先通过函数RndNum(int VcodeNum)产生自定长度的汉字,输入参数为生成汉字长度。如果需要加入生成带字母和数字的随机字符串,在str变量里加入字母和数字就可以了。具体代码如下:
/// 随机产生指定长度的汉字
public String RndNum(int VcodeNum)
{
string str = "的一是在不了有和…红细引听该铁价严";//常用汉字
string code = "";
Random rd = new Random();
int i;
for (i = 0; i < VcodeNum; i++)
{
code += str.Substring(rd.Next(0, str.Length), 1);
}
return code;
}
然后根据随机汉字字符串生成验证图片,创建一个Bmp位图,从枚举类型Colors和Fonts里随机生成Brush和Font,用该Brush和Font绘制验证信息。并在其上增加背景噪音线、前景噪音点和边框线。具体代码如下:
///生成图片(增加背景噪音线、前景噪音点和边框线)
///<param name="checkCode">随机字符串</param>
public byte[] CreateVerifyCodeImage(string checkCode)
{
if (checkCode.Trim() == "" || checkCode == null)
return null;
int Padding = 2;
int fontSize = 28;
Color[] Colors = { Color.Black, Color.Red, Color.DarkBlue,
Color.Green, Color.Orange, Color.Brown, Color.DarkCyan, Color.Purple
};
string[] Fonts = { "Arial", "Georgia", "宋体", "黑体", "幼圆" };
int fontWidth = fontSize + Padding;
int imageWidth = (int)(checkCode.Length * (fontWidth)*1.4) + Padding
* 2;
int imageHeight = 40;
//创建Bmp位图
Bitmap image = new Bitmap(imageWidth, imageHeight);
Graphics g = Graphics.FromImage(image);
try
{
g.Clear(Color.White);
Random rand = new Random();
// 画图片的背景噪音线
int c = 3 * 20;
for (int i = 0; i < c; i++)
{
int x1 = rand.Next(image.Width);
int x2 = rand.Next(image.Width);
int y1 = rand.Next(image.Height);
int y2 = rand.Next(image.Height);
g.DrawLine(new Pen(Color.Silver), x1, y1, x2, y2);
}
int left = 0, top = 0, top1 = 1, top2 = 1;
int n1 = (imageHeight - fontSize - Padding * 2*2);
int n2 = n1 / 4;
top1 = n2;
top2 = n2 * 2;
Font f;
Brush b;
int cindex, findex;
//随机字体和颜色的验证码字符
for (int i = 0; i < checkCode.Length; i++)
{
cindex = rand.Next(Colors.Length - 1);
findex = rand.Next(Fonts.Length - 1);
f = new Font(Fonts[findex], fontSize, FontStyle.Bold);
b = new SolidBrush(Colors[cindex]);
if (i % 2 == 1)
{ top = top2; }
else
{ top = top1; }
left = (int)(i *fontWidth*1.4) ;
g.DrawString(checkCode.Substring(i, 1), f, b, left, top);
//画图片的前景噪音点Orange
g.DrawRectangle(new Pen(Color.Orange), 0, 0, image.Width - 1,
image.Height - 1);
}
//画一个边框边框颜色为Color.Gainsboro
g.DrawRectangle(new Pen(Color.Gainsboro, 0), 0, 0, image.Width - 1,
image.Height - 1);
g.Dispose();
//产生波形
image = TwistImage(image, true, 6, 4);
MemoryStream ms = new MemoryStream();
image.Save(ms, Imaging.ImageFormat.Gif);
return ms.ToArray();
}
catch
{
g.Dispose();
image.Dispose();
return null;
}
}
生成的图片其实就是验证码图片,应该说已经达到了图片验证码效果了。通过OCR识别技术还是能自动分析出图片中验证码字符串信息的。为了加强图片的抗干扰能力,需要对图片进行波形扭曲,生成的验证码图片如图1所示,波形扭曲实现代码如下:
/// 正弦曲线Wave扭曲图片
/// <param name="srcBmp">图片</param>
/// <param name="bXDir">如果扭曲则选择为True</param>
///<param name="nMultValue">波形的幅度倍数,越大扭曲的程度越高,一般为5</param>
/// <param name="dPhase">波形的起始相位,取值区间[0-2*PI)</param>
protected Bitmap TwistImage(Bitmap srcBmp, bool bXDir, double
dMultValue, double dPhase)
{
Bitmap destBmp = new Bitmap(srcBmp.Width, srcBmp.Height);
// 将位图背景填充为白色
Graphics graph = Graphics.FromImage(destBmp);
graph.FillRectangle(new SolidBrush(Color.Silver), 0, 0,
destBmp.Width, destBmp.Height);
graph.Dispose();
double dBaseAxisLen = bXDir ? (double)destBmp.Height : (double)destBmp.Width;
for (int i = 0; i < destBmp.Width; i++)
{
for (int j = 0; j < destBmp.Height; j++)
{
double dx = 0;
dx = bXDir ? (PI2 * (double)j) / dBaseAxisLen : (PI2 * (double)i) /
dBaseAxisLen;
dx += dPhase;
double dy = Math.Sin(dx);
// 取得当前点的颜色
int nOldX = 0, nOldY = 0;
nOldX = bXDir ? i + (int)(dy * dMultValue) : i;
nOldY = bXDir ? j : j + (int)(dy * dMultValue);
Color color = srcBmp.GetPixel(i, j);
if (nOldX >= 0 && nOldX < destBmp.Width
&& nOldY >= 0 && nOldY < destBmp.Height)
{
destBmp.SetPixel(nOldX, nOldY, color);
}
}
}
return destBmp;
}

图1 Web方式系统登录界面
四、Web登录
整个Web登录过程由两个页面完成,分别是登录页面login.aspx和验证码生成页面idencode.aspx。在登录页面login.aspx上添加三个TextBox控件、一个Image控件和一个Button控件。他们的名称分别为:txtUserName、txtPassWord、txtCode、imgVerify和BtnSubmit,如图1所示。imgVerify用于显示验证码图片,因此它的src值为idencode.aspx,也即由idencode.aspx来生成具体的验证码图片。
在idencode.aspx页的Page_Load写入如下代码:
Passport.VerifyCode vc = new Passport.VerifyCode();
String codeNum=vc.RndNum(3);
Session["DeanIdenCode"] = codeNum;
byte[] ImageStream = vc.CreateVerifyCodeImage(codeNum);
Response.ClearContent();
Response.ContentType = "image/Gif";
Response.BinaryWrite(ImageStream);
程序首先创建随机的汉字验证码字符串,并把这个验证码字符串保存在Session变量里面。再由
CreateVerifyCodeImage(string checkCode)函数生成验证图像数据流。通过Response的BinaryWrite的方法将图像数据流输出给客户端,即页面。完成了网页验证码生成的开发。
在页面Login.aspx上控件BtnSubmit的BtnSubmit_Click事件上校验输入的验证码是否正确,用户名和密码输入是否正确。密码输入后需要经过Encrypt(string
stringToEncrypt,string sEncryptionKey)函数加密后再和数据库存储的密码进行比对。主要代码如下:
//检查验证码信息,验证用户名、密码信息
string username = txtUserName.Text;
string password = txtPassWord.Text;
string code = txtCode.Text.ToLower();
if (String.Compare(Session["DeanIdenCode"].ToString(), code, true)
!= 0)
{
MsgLbl.Text = "验证码输入错误";
return;
}
//采用DES加密密码
password = CryptoTool.DESCryptography.Encrypt(password,
ConstConfig.sEncryptionKey);
if (UserInfo.CheckUserPassword(username, password))
{ //登录成功,记录用户登录状态信息
……
}
else
{ MsgLbl.Text = "密码或用户名错误,请重新登陆";}
以上系统登录通过验证码和密码加密技术很好的解决了系统登录时的安全问题。需要注意的是没有考虑网络传输时的安全,建议采用SSL传输。
五、通过WebService提供登录接口
除了直接输出验证码信息,直接连接数据库验证用户名和密码信息外。在很多系统中,系统的登录部分往往会独立出来,提供一个登录接口,供很多受信任的客户端调用。这些客户端可能是Web网页形式的,也可能是WinForm形式的。而开发语言也有多种,如C#、VB.Net、Delphi(严格的说是Pascal)等。
提供Web
Service登录接口是最好的解决方案,通过标准的XML形式来交换数据使接口和平台开发语言无关。在实例中整个登录过程需要提供两个WebMethod函数,也即是Web
Service提供给客户端调用者的接口方法,一个是UserPreLogin,获取验证码图像信息;一个是UserLogin,验证登录请求。
1. 登录接口
新建一个Web Service项目,命名为WebServices。新增Web Service项UserLoginService.asmx,添加2个方法。其中UserPreLogin函数生成验证码图片信息和客户端识别码,先通过随机生成办法产生验证码字符信息并保存在Session变量里面,随后生成验证码图片字节流并作为函数结果返回。由Guid生成客户端识别码,通过out参数方式返回给客户端。注意UserPreLogin函数需要通过Session保存验证码字符串strCode信息和客户端识别码ClientKey信息供验证登录请求时使用。这就需要打开WebMethod属性EnableSession=true使其支持WebService中的变量做到跨方法使用,也即Session变量VerifyCode_13590和ClientKey_13590在UserLogin函数还可以使用,并能读取保存的值。客户端用CookieContainer来关联Session。UserPreLogin实现代码如下:
/// 生成验证码和识别码
/// <param name="ClientName">受信任的客户端标识</param>
/// <param name="ClientKey">给客户端产生一个GUID识别码</param>
/// <returns>返回验证码图片字节流</returns>
[WebMethod(EnableSession=true)]
public byte[] UserPreLogin(string ClientName, out string ClientKey)
{
……//判断是否是受信任的客户端,非信任客户端直接返回退出
Passport.VerifyCode vCode = new Passport.VerifyCode();
string strCode = vCode.RndNum(3);
byte[] ImageStream = vCode.CreateVerifyCodeImage(strCode);
Session["VerifyCode_13590"] = strCode;
ClientKey = Guid.NewGuid().ToString();
Session["ClientKey_13590"] = ClientKey;
return ImageStream;
}
UserLogin提供登录请求验证,如果登录成功返回用户ID号。程序先判断输入的验证码字符、识别码和Session变量保存的值是否一样,再验证用户名和密码,并记录登录的日志信息。通过out类型参数ErrorInfo、WarningInfo返回程序运行的错误信息和警告信息。实现代码如下:
/// 验证登录请求
/// <param name="LoginName">登录名</param>
/// <param name="Password">密码</param>
/// <param name="VerifyCode">验证码</param>
/// <param name="ClientKey">识别码</param>
/// <param name="ErrorInfo">错误信息</param>
/// <param name="WarningInfo">警告信息</param>
/// <returns>返回用户ID号,如为0表示错误</returns>
[WebMethod(EnableSession = true)]
public int UserLogin(string LoginName, string Password, string
VerifyCode, string ClientKey, out string ErrorInfo, out string
WarningInfo)
{
int refalse = 0;
ErrorInfo = ""; WarningInfo = "";
string userID = "0";
string newCode = string.Empty;
……//检查用户名、密码、验证码和识别码是否为空
if (Session["VerifyCode_13590"] == null)
{
ErrorInfo = "Session有问题";
return refalse;
}
else
{
newCode = Session["VerifyCode_13590"].ToString();
}
if (!newCode.ToLower().Equals(VerifyCode.ToLower()))
{
ErrorInfo = "验证码输入错误";
return refalse;
}
if (Session["ClientKey_13590"] == null)
{
ErrorInfo = "Session有问题";
return refalse;
}
else if (!ClientKey.Equals(Session["ClientKey_13590"].ToString()))
{
ErrorInfo = "识别码输入错误";
return refalse;
}
//验证用户名和密码
if (UserInfo.CheckUserPassword(LoginName, Password))
{
DataRow dr = UserInfo.GetUserInfo(LoginName);
string userType = string.Empty;
if (dr != null)
{
userID = dr["fUID"].ToString();
userType = dr["fUserType"].ToString();
}
//添加日志
LogHelper.AddUserLog(LogType.Info, Request.UserHostAddress,LoginName,"系统",
"登陆成功!");
Session.Remove("VerifyCode_13590");
Session.Remove("ClientKey_13590");
return int.Parse(userID);
}
else
{
ErrorInfo = "用户名或者密码错误";
return refalse;
}
}
2. 客户端调用
根据提供的WebService登录接口可以供多种程序来调用,如C#、VB、Java等B/S、C/S结构的程序。以C#语言开发客户端调用实例详细了解通过WebService登录接口开发用户登录系统的具体实现过程。
建立一个新的Windows Forms项目,命名为DeanClient。增加2个Form窗体frmLogin.cs和frmMain.cs。frmLogin为系统登录窗口,frmMain为登录成功后进入的主程序界面。在frmLogin窗体界面上添加控件TextBox、PictureBox和Button,如图2所示。增加Web引用,URL为
http://localhost:2008/WebServices/UserLoginService.asmx,引用名为LoginService。系统会自动生成代理类Reference.cs。
向项目里添加类ClientUserLogin.cs,编写调用业务逻辑。先实例化Web服务对象,再实例化CookieContainer类为CookiePassport,该对象为Cookie类的实例提供存储空间,用来关联Session。即在调用ulservice.UserPreLogin后把ulservice对象的CookieContainer赋值给CookiePassport,在调用ulservice.UserLogin前把CookiePassport再赋值回ulservice.
CookieContainer。保证服务器端的Web Service中的Session值不会丢失。具体代码如下:
//实例化Web服务对象
private static LoginService.UserLoginService ulservice = new
LoginService.UserLoginService();
//受信任的客户端标识
private static string ClientName = "DeanClient 1.0";
// 上下传递用的识别码
public static string ClientKey = string.Empty;
//使用的Cookie 集合,不保证每个Cookie 的名字不发生变化。所以采用这种方式
public static System.Net.CookieContainer CookiePassport = new
System.Net.CookieContainer();
// 获得验证码和识别码,返回验证码字节流
public static Image UserPreLogin()
{
ulservice.CookieContainer = new System.Net.CookieContainer();
byte[] ImageStream = ulservice.UserPreLogin(ClientName, out
ClientKey);
CookiePassport = ulservice.CookieContainer;
Image newImage;
using (MemoryStream ms = new MemoryStream(ImageStream, 0,
ImageStream.Length))
{
ms.Write(ImageStream, 0, ImageStream.Length);
newImage = Image.FromStream(ms, true);
}
return newImage;
}
/// 登录请求
/// <param name="LoginName">登录用户名</param>
/// <param name="Password">密码</param>
/// <param name="VerifyCode">验证码</param>
public static int UserLogin(string LoginName, string Password,
string VerifyCode)
{
string ErrorInfo, WarningInfo;
ulservice.CookieContainer = CookiePassport;
//密码加密
Password = CryptoTool.DESCryptography.Encrypt(Password,
ConstConfig.sEncryptionKey);
int t = ulservice.UserLogin(LoginName, Password, VerifyCode,
ClientKey, out ErrorInfo, out WarningInfo);
CookiePassport = ulservice.CookieContainer;
if (t==0)
{
MsgBox.ShowError(ErrorInfo, "登录错误信息");
CookiePassport = null; return 0;
}
else
{
if ((WarningInfo != null) && (WarningInfo.Length > 0))
{
MsgBox.ShowWarning(WarningInfo, "登录警告信息"); }
return t;
}
}
frmLogin窗体直接调用ClientUserLogin类的静态方法来实现系统登录,在窗体的Load事件里加入代码:
picExPwd.Image = ClientUserLogin.UserPreLogin();
txtUserName.Focus();
窗体一启动就载入验证码图片信息。登录按钮的Click事件里面加入代码:
//校验输入数据
if (!ValidCheck())
{ return ; }
string LoginUserName = txtUserName.Text.Trim();
string UserPassword = txtPassword.Text.Trim();
string VerifyCode = txtExPwd.Text.Trim();
int t = ClientUserLogin.UserLogin(LoginUserName, UserPassword,
VerifyCode);
if (t == 0)
{ picExPwd.Image = ClientUserLogin.UserPreLogin();
btnLogin.Enabled = true;
return;
}
else
{ userID = t;
this.Close();
}
编译运行程序,如图2所示。B/S结构Web页面方式的调用方法也和此类似。只是验证码图片的显示方式不同,是通过前面第四部分描述的靠Response输出显示。用Web
Service接口来实现登录过程,依靠XML的通用性,语言、平台无关性,把登录的业务逻辑判断隔离开来。如加入权限控制模块可以实现单点登录。在客户端把密码加密再进行传输有效的保证了密码的安全。通过加入验证码有效的阻挡了攻击。

图2 WinForm调用登录界面
六、使用硬件实现双因子认证
通过“用户名+密码”的方式来认证识别用户,以及引入密码加密技术和验证码技术都是通过软件的方式来实现。为了进一步提高登录的安全,通用的做法是引入硬件设备。如智能卡,USB
Key等。最常用的是USB Key,因为USB Key直接使用计算机标准接口,模样跟普通的U盘差不多,携带方便。USB
Key的安全性主要体现在以下三个方面:
1. 硬件PIN码保护
黑客需要同时取得用户的USB Key硬件以及用户的PIN码,才可以登录系统。即使用户的PIN码被泄漏,只要用户持有的USB
Key不被盗取,合法用户的身份就不会被仿冒;如果用户的USB Key遗失,拾到者由于不知道用户PIN码,也无法仿冒合法用户的身份。
2. 安全的存储介质
USB Key的密钥存储于安全的介质之中,外部用户无法直接读取,对密钥文件的读写和修改都必须由USB Key内的程序调用。从USB
Key接口的外面,没有任何一条命令能够对密钥区的内容进行读出、修改、更新和删除。
3. 硬件实现加密算法
USB Key内置CPU或智能卡芯片,可以实现数据摘要、数据加解密和签名的各种算法,加解密运算在USB
Key内进行,保证了用户密钥不会出现在计算机内存中。
七、结语
本文所有程序在VS.Net2008下测试通过。其实例具有普遍性,对于常用系统的登录验证技术作了介绍。系统的安全性是一个持续完善的过程,所谓“魔高一尺,道高一丈”,在设计登录系统时需考虑多方面的因素。
参考文献:
1 MSDN.
2 http://www.cnblogs.com.
3 郝刚 . ASP.NET 2.0开发指南[M].北京:人民邮电出版社,2006
(电脑编程技巧与维护杂志社版权所有。未经许可不得转载。)
|
| |
|
|