基于C#和JavaScript的网页验证码优化实现方式(不用Session和Cookie)

来源:互联网 发布:淘宝新手卖家活动 编辑:程序博客网 时间:2024/05/22 01:42

    在使用常规方式解决验证码的问题时,通常会遇到这样的问题:Session对于服务器资源耗费很严重,而且当多个客户端同时使用Session时,它的值不能与每个客户端同步,这就导致了将有客户端无故产生“验证码错误”的问题。而Cookie的限制性和局限性也不言而喻,有的用户甚至根本不支持。

    经过反复考究和试验,遂产生了优化验证码实现方式的想法。我决定使用少量的JavaScript,C#服务端产生验证码并进行验证,并依赖简单的数据库功能。

    基本过程是这样:

    1.网页加载时或刷新验证码时,JavaScript产生一个新的验证ID,这个ID须尽可能保持唯一,它是我们验证答案时与客户端对应的依据,也就是说,只有客户端将ID和答案同时提交上来,我们才能在答案库中检索到这个用户当前网页的验证数据。需要注意的是,这个ID在同一用户的各个网页之间也是不同的,这就可以实现多网页同时验证而不会导致冲突。

    2.产生新的ID后,JavaScript将这个ID记录在验证表单的一个隐藏的Input控件中,籍以与答案同时提交。然后以这个ID作为参数,刷新验证码图片。(生成新的图片资源链接,比如:"/Verify.aspx?ID=" + ID)。

    3.服务端的Verify.aspx是验证码图片的生成器,它随机生成一个新的答案,并根据客户端提交的ID在数据库中增加一个记录,这个记录包含 ①验证ID ②产生验证的时间 ③验证答案,然后以新答案产生一张图片回应给客户端(数据流)。这个第3步是关键所在。

    4.客户端网页提交后,服务端根据ID在数据库中查询相应的记录,并检查客户端提交的答案和数据库中的答案是否相符,这是完成验证的最后阶段。为了防止客户端恶意截取ID反复验证,我们在这里要检查验证的时间是否超过我们规定的限制,比如10分钟。

    5.为了避免数据库资源开销,我们还需要在服务器上定时清理已经超时作废的验证记录。

    需要注意的是,JavaScript产生的ID在验证过程中会有两次提交,第一次是在生成验证码图片时,Verify.aspx接收到这个ID生成答案,并在数据库中记录,然后产生图片;第二次是提交验证的时候,服务器根据这个ID查询答案是否正确。两次缺一不可。用户看到的验证码图片其实是一个微型页面,它同时负责记录数据和生成图片的工作,用户界面得到的只是后者,这两个工作在它的src属性改变时会自动执行。

    这种实现方式的好处是非常显著的,经测试,多客户端和同一客户端的多网页之间,可以随意同步进行验证而互不冲突,这弥补了Session值唯一性的缺陷,也大大降低了服务器开销。


    JavaScript需要依赖jquery的一小部分功能,现贴上代码

   

<script type="text/javascript">$(document).ready(function () {    RefreshVerifyImage();});function RefreshVerifyImage() {    var ID = "";    for (var i = 0; i < 9; i++) {        ID = ID + Math.floor(Math.random() * 9);    }    var IDBox = document.getElementById("VerifyID");    IDBox.value = ID;    var CodeImage = document.getElementById("getcode");    CodeImage.src = "/Verify.aspx?VerifyID=" + ID;}</script>

这部分JavaScript会在网页加载完毕时自动执行,也会在用户点击“换一张”链接时被动执行,但需要加载jquery。图片控件的src属性在改变时会自动刷新,这会使服务器上的Verify.aspx记录验证ID,并产生答案和图片,将图片回应给用户。


    包含验证码的网页部分代码:

<form action="/Login" id="loginform" method="post">    <label style="display: none;font-size:12px;margin-top:8px;">验证码</label>    <input id="txtCaptcha" name="txtCaptcha" maxlength="4" size="10" type="text" value="" />    <img ID="getcode" src="/" />    <A href="javascript:RefreshVerifyImage()">        <span class="v_middle" style="padding-left: 15px;">换一张</span>    </A>    <input id="VerifyID" maxlength="9" name="VerifyID" type="text" value="" style="position:absolute;visibility:hidden;"/></form>

    其中的图片控件getcode就是产生验证码并记录的微型网页,它的src属性在开始时是空的,由JavaScript产生新的ID后进行刷新,而“换一张”链接的点击也会导致它刷新。刷新是通过RefreshVerifyImage函数实现的。

    C#中的验证实现类:(直接粘贴的,格式有点变形,请谅解)

              public class Verify{public const int EffectiveSeconds = 35;    //验证数据的有效时间(秒),避免重复提交验证public const int RecordSaveMinutes = 10;    //验证数据在数据库中的保存时间,用于定时清理public const int Length_Answer = 4;    //规定验证答案的长度/// <summary>/// 按照指定ID创建一个新的验证码,返回答案/// </summary>/// <param name="ID">验证ID</param>/// <returns></returns>public static string Create(string ID){string Answer = ""; Random rnd; int number1, number2;SqlCommand cmd; SqlDataReader dr; string sql;sql = "SELECT * FROM [Verify] WHERE [ID]='" + ID + "'";using (cmd = new SqlCommand(sql, Conn)){using (dr = cmd.ExecuteReader()){if (dr.Read()) { Answer = dr["Answer"].ToString(); }}}if (string.IsNullOrEmpty(Answer)){rnd = new Random();for (int i = 0; i < Length_Answer; i++){number1 = rnd.Next(0, 9);if (number1 < 5){number2 = rnd.Next(0, 9);Answer += number2;}else{number2 = rnd.Next(97, 122);Answer += ((char)number2).ToString();}}rnd = null;try{sql = "INSERT [Verify]([ID],[Time],[Answer]) VALUES(" + "'" + ID + "'," + "'" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + "'," + "'" + Answer + "'" + ")";using (cmd = new SqlCommand(sql, Conn)){cmd.ExecuteNonQuery();}}catch { }}return Answer;}/// <summary>/// 根据验证ID获取图片答案/// </summary>/// <param name="ID">验证ID</param>/// <param name="CheckTime">是否验证超时</param>/// <returns></returns>public static string GetAnswer(string ID, bool CheckTime){string result = "";string sql = "SELECT * FROM [Verify] WHERE [ID]='" + ID + "'";using (SqlCommand cmd = new SqlCommand(sql, Conn)){using (SqlDataReader dr = cmd.ExecuteReader()){if (dr.Read()){DateTime Time = (DateTime)dr["Time"];if (CheckTime){if ((DateTime.Now - Time).TotalSeconds > EffectiveSeconds)throw new Exception("验证请求已超时,请重新提交");}result = dr["Answer"].ToString();}else{throw new Exception("无效的验证请求数据");}}}return result;}/// <summary>/// 清理已经超期无用的验证记录/// </summary>public static void ClearWasteRecords(){try{DateTime EarliestTime = DateTime.Now.AddMinutes(-RecordSaveMinutes);string sql = "DELETE FROM [Verify] WHERE [Time]<'" + EarliestTime.ToString("yyyy-MM-dd HH:mm:ss") + "'";using (SqlCommand cmd = new SqlCommand(sql, Conn)){cmd.ExecuteNonQuery();}}catch { }}}


    检查验证码的服务端C#代码:

string VerifyAnswer = Verify.GetAnswer(Request["VerifyID"].ToString(), CheckTime: true);if (string.IsNullOrEmpty(Code) || Code.ToLower().Trim() != VerifyAnswer.ToLower().Trim())    throw new Exception("验证码不正确,请重新输入!");Comlife.Verify.ClearWasteRecords();


    数据库表的结构:

    ID          varchar(9)   验证码序列号

    Time     datetime   创建时间

    Answer  varchar(4)   验证码答案

   


    Verify.aspx绘制验证码图片的过程因为太过冗繁,不便在此贴出,如有需要可向我单独索取。


    到此全部结束,如果有不明白或测试出现错误,请联系我

    QQ:24579039


    一点拙见,还请各位高手不要嗤之以鼻以致谩骂,如果有更好的想法也欢迎交流。




0 0
原创粉丝点击