首先背景是exchange的邮箱系统没有后台源代码。因为这个原因,生成验证码的机制放在aspx的runat="sever"后台代码里面。
首先需要找到iis中logon.aspx文件。在这里找到输入邮箱名和密码的input元素,对应增加上输入验证码的input和显示验证码图片的img元素。
需要增加两个文件:VerifyCode.aspx是用户输入进行输入的验证码验证操作的代码;GetImg.aspx是用于显示验证码图片的,即将之前添加的img的src设置为这个GetImg.aspx即可。至于点击img之后自动刷新,则属于体验性的改进。
代码具体执行逻辑是GetImg在load的时候,将随机生成的验证码加密之后,存在客户端浏览器的cookie中,同时创建一个Img对象,将4位验证码字符按顺序输出到img对象上,同时img随机分布一些点pixel,之后图片的stream返回到浏览器上。
而VerifyCode的验证则是则根据输入的内容和之前的cookie存的密文解密之后进行比较,如果一致,则通过验证。
VerifyCode.aspx代码如下:
<%@ Page Language="C#" AutoEventWireup="true" Debug="true" %> <%@ Import Namespace="System.Security.Cryptography" %> <script runat="server"> protected void Page_Load(object sender, EventArgs e) { if (Request.Cookies["yzmCode"] != null && Request.QueryString["yzmc"] != null) { string code = Decrypt(Request.Cookies["yzmCode"].Value).ToUpper(); //Response.Write("code"+code+"\n"); //Response.Write("code"+code+"\n"); //Response.End(); string ucode = Request.QueryString["yzmc"].ToUpper(); if (code == ucode) { Response.Write("ok"); Response.End(); } else { Response.Write("error"); Response.End(); } } else { Response.Write("error2"); Response.End(); } } public static string Decrypt(string Text) { string sKey = "Exchange"; DESCryptoServiceProvider des = new DESCryptoServiceProvider(); int len; len = Text.Length / 2; byte[] inputByteArray = new byte[len]; int x, i; for (x = 0; x < len; x++) { i = Convert.ToInt32(Text.Substring(x * 2, 2), 16); inputByteArray[x] = (byte)i; } des.Key = ASCIIEncoding.ASCII.GetBytes(System.Web.Security.FormsAuthentication.HashPasswordForStoringInConfigFile(sKey, "md5").Substring(0, 8)); des.IV = ASCIIEncoding.ASCII.GetBytes(System.Web.Security.FormsAuthentication.HashPasswordForStoringInConfigFile(sKey, "md5").Substring(0, 8)); System.IO.MemoryStream ms = new System.IO.MemoryStream(); CryptoStream cs = new CryptoStream(ms, des.CreateDecryptor(), CryptoStreamMode.Write); cs.Write(inputByteArray, 0, inputByteArray.Length); cs.FlushFinalBlock(); return Encoding.Default.GetString(ms.ToArray()); } </script>
GetImg.aspx代码如下:
<%@ Page Language="C#" AutoEventWireup="true" %> <%@ Import Namespace="System.Drawing" %> <%@ Import Namespace="System.Drawing.Imaging" %> <%@ Import Namespace="System.IO" %> <%@ Import Namespace="System.Security.Cryptography" %> <script runat="server"> public static string Encrypt(string Text) { string sKey = "Exchange"; DESCryptoServiceProvider des = new DESCryptoServiceProvider(); byte[] inputByteArray; inputByteArray = Encoding.Default.GetBytes(Text); des.Key = ASCIIEncoding.ASCII.GetBytes(System.Web.Security.FormsAuthentication.HashPasswordForStoringInConfigFile(sKey, "md5").Substring(0, 8)); des.IV = ASCIIEncoding.ASCII.GetBytes(System.Web.Security.FormsAuthentication.HashPasswordForStoringInConfigFile(sKey, "md5").Substring(0, 8)); System.IO.MemoryStream ms = new System.IO.MemoryStream(); CryptoStream cs = new CryptoStream(ms, des.CreateEncryptor(), CryptoStreamMode.Write); cs.Write(inputByteArray, 0, inputByteArray.Length); cs.FlushFinalBlock(); StringBuilder ret = new StringBuilder(); foreach (byte b in ms.ToArray()) { ret.AppendFormat("{0:X2}", b); } return ret.ToString(); } public static string Decrypt(string Text) { string sKey = "Exchange"; DESCryptoServiceProvider des = new DESCryptoServiceProvider(); int len; len = Text.Length / 2; byte[] inputByteArray = new byte[len]; int x, i; for (x = 0; x < len; x++) { i = Convert.ToInt32(Text.Substring(x * 2, 2), 16); inputByteArray[x] = (byte)i; } des.Key = ASCIIEncoding.ASCII.GetBytes(System.Web.Security.FormsAuthentication.HashPasswordForStoringInConfigFile(sKey, "md5").Substring(0, 8)); des.IV = ASCIIEncoding.ASCII.GetBytes(System.Web.Security.FormsAuthentication.HashPasswordForStoringInConfigFile(sKey, "md5").Substring(0, 8)); System.IO.MemoryStream ms = new System.IO.MemoryStream(); CryptoStream cs = new CryptoStream(ms, des.CreateDecryptor(), CryptoStreamMode.Write); cs.Write(inputByteArray, 0, inputByteArray.Length); cs.FlushFinalBlock(); return Encoding.Default.GetString(ms.ToArray()); } protected void Page_Load(object sender, EventArgs e) { int codeW = 80; int codeH = 22; int fontSize = 16; string chkCode = string.Empty; Color[] color = { Color.Black, Color.Red, Color.Blue, Color.Green, Color.Orange, Color.Brown, Color.Brown, Color.DarkBlue }; string[] font = { "Times New Roman", "Verdana", "Arial", "Gungsuh", "Impact" }; char[] character = { ‘2‘, ‘3‘, ‘4‘, ‘5‘, ‘6‘, ‘8‘, ‘9‘, ‘a‘, ‘b‘, ‘d‘, ‘e‘, ‘f‘, ‘h‘, ‘k‘, ‘m‘, ‘n‘, ‘r‘, ‘x‘, ‘y‘, ‘A‘, ‘B‘, ‘C‘, ‘D‘, ‘E‘, ‘F‘, ‘G‘, ‘H‘, ‘J‘, ‘K‘, ‘L‘, ‘M‘, ‘N‘, ‘P‘, ‘R‘, ‘S‘, ‘T‘, ‘W‘, ‘X‘, ‘Y‘ }; Random rnd = new Random(); for (int i = 0; i < 4; i++) { chkCode += character[rnd.Next(character.Length)]; } //Session["yzmCode"] = chkCode; HttpCookie cook = new HttpCookie("yzmCode", Encrypt(chkCode)); cook.Expires = DateTime.Now.AddMinutes(20); Response.Cookies.Add(cook); Bitmap bmp = new Bitmap(codeW, codeH); Graphics g = Graphics.FromImage(bmp); g.Clear(Color.White); for (int i = 0; i < 1; i++) { int x1 = rnd.Next(codeW); int y1 = rnd.Next(codeH); int x2 = rnd.Next(codeW); int y2 = rnd.Next(codeH); Color clr = color[rnd.Next(color.Length)]; g.DrawLine(new Pen(clr), x1, y1, x2, y2); } for (int i = 0; i < chkCode.Length; i++) { string fnt = font[rnd.Next(font.Length)]; Font ft = new Font(fnt, fontSize); Color clr = color[rnd.Next(color.Length)]; g.DrawString(chkCode[i].ToString(), ft, new SolidBrush(clr), (float)i * 18 + 2, (float)0); } for (int i = 0; i < 100; i++) { int x = rnd.Next(bmp.Width); int y = rnd.Next(bmp.Height); Color clr = color[rnd.Next(color.Length)]; bmp.SetPixel(x, y, clr); } Response.Buffer = true; Response.ExpiresAbsolute = System.DateTime.Now.AddMilliseconds(0); Response.Expires = 0; Response.CacheControl = "no-cache"; Response.AppendHeader("Pragma", "No-Cache"); MemoryStream ms = new MemoryStream(); try { bmp.Save(ms, ImageFormat.Png); Response.ClearContent(); Response.ContentType = "image/Png"; Response.BinaryWrite(ms.ToArray()); } finally { bmp.Dispose(); g.Dispose(); } } </script>
注意:加密解密对应的sKey变量要一致。
判断验证码是否输入正确的js逻辑(简单点描述就是发起get请求,地址是上面提到的VerifyCode.aspx,参数和代码中对应上即可):
var codeVaule = $("#yzm").val(); if(codeVaule == ""){ $("#yzm-tip").html("验证码不能为空"); return false; }else if(codeVaule.length!=4){ $("#yzm-tip").html("验证码位数不正确"); return false; }else{ $.get("VerifyCode.aspx?yzmc="+codeVaule,{},function(data){ if(data=="ok"){ $("#yzm-tip").html("验证码正确"); //$(".btnSignin").click(); clkLgn();//登录逻辑 bo=true; }else{ $("#yzm-tip").html("验证码错误!"); } }); } return bo;
(为了简单,使用了jquery,引用即可)
触发登录操作的js函数是clkLgn(),这个放在flogon.js这个脚本文件里面,由于存在点击登录按钮和直接回车(e.keyCode == 13)直接执行登录的两种场景,因此此处需要看清js代码。具体倒没什么难度。
可以修改clkLgn()的代码逻辑,直接在里面增加对输入验证码进行验证的逻辑,然后确定是否走真正的登录的代码。也可以在点击登录按钮或者回车的两处逻辑上分别走验证请求再处理。
本身加上验证码的目的只是为了防止密码撞库,快速达到要求即可。
点击验证码图片自动刷新的改进:
function refreshImg(){ $("#yzmImg")[0].src="GetImg.aspx?"+Math.random(); }