背景: 因为移动端APP和Msite手机注册发送短信验证码没有添加图片验证码功能。公司的短信接口被恶意刷取。所以我们就觉得在移动端添加一个图片验证码功能。分享一下大体实现方式思路。PS demo是自己写的。跟公司代码还是有很大差距的。
一. 图片验证码第一版
1. 建立图片验证码 ValidationCodeHelper
1.1 填写方法生成对应的.验证码: 默认是4位数字
1 private static char[] _constant = { 2 '0','1','2','3','4','5','6','7','8','9', 3 'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z', 4 'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z' 5 }; 6 7 public static string CreateValidateCode(int length = 4, bool isNum = true) 8 { 9 var sb = new StringBuilder(); 10 var constant = isNum ? _constant.Take(10).ToArray() : _constant; 11 var constant_count = constant.Count(); 12 Random rd = new Random(); 13 for (var index = 0; index < length; index++) 14 { 15 sb.Append(constant[rd.Next(constant_count)]); 16 } 17 return sb.ToString(); 18 }
1.2 通过验证码生成图片流, 此代码是从其他博友那里Copy过来的。自己对图片方面不擅长
1 public static byte[] GetImage(string code) 2 { 3 Bitmap image = new Bitmap((int)Math.Ceiling(code.Length * 16.0), 27); 4 Graphics g = Graphics.FromImage(image); 5 try 6 { 7 Random random = new Random(); 8 g.Clear(Color.Gray); 9 for (int i = 0; i < 25; i++) 10 { 11 int x1 = random.Next(image.Width); 12 int x2 = random.Next(image.Width); 13 int y1 = random.Next(image.Height); 14 int y2 = random.Next(image.Height); 15 g.DrawLine(new Pen(Color.Silver), x1, x2, y1, y2); 16 } 17 Font font = new Font("Arial", 13, (FontStyle.Bold | FontStyle.Italic)); 18 LinearGradientBrush brush = new LinearGradientBrush(new Rectangle(0, 0, image.Width, image.Height), Color.Blue, Color.DarkRed, 1.2f, true); 19 g.DrawString(code, font, brush, 3, 2); 20 for (int i = 0; i < 100; i++) 21 { 22 int x = random.Next(image.Width); 23 int y = random.Next(image.Height); 24 image.SetPixel(x, y, Color.FromArgb(random.Next())); 25 } 26 g.DrawRectangle(new Pen(Color.Silver), 0, 0, image.Width - 1, image.Height - 1); 27 MemoryStream stream = new MemoryStream(); 28 image.Save(stream, ImageFormat.Jpeg); 29 return stream.ToArray(); 30 } 31 catch (Exception ex) 32 { 33 return null; 34 } 35 finally 36 { 37 g.Dispose(); 38 image.Dispose(); 39 } 40 }
2. 封装一个简单的CookieHelper类型主要是对Cookie进行加密。只是简单封装, 没有对CookieHelper添加泛型,未支持Object处理。公司里面的CookieHelper库更强大。可是不能分享代码出来。所以自己简单的写了一个。
1 public class CookieHelper 2 { 3 4 #region 字符串加密解密 5 6 private static string _MD5 = "8ff0c65d-a2ed-4e1e-af85-690c08b8d039"; 7 8 private static string Decrypt(string cipherString) 9 { 10 byte[] keyArray; 11 byte[] toEncryptArray = Convert.FromBase64String(cipherString); 12 MD5CryptoServiceProvider hashmd5 = new MD5CryptoServiceProvider(); 13 keyArray = hashmd5.ComputeHash(UTF8Encoding.UTF8.GetBytes(_MD5)); 14 hashmd5.Clear(); 15 16 TripleDESCryptoServiceProvider tdes = new TripleDESCryptoServiceProvider(); 17 tdes.Key = keyArray; 18 tdes.Mode = CipherMode.ECB; 19 tdes.Padding = PaddingMode.PKCS7; 20 ICryptoTransform cTransform = tdes.CreateDecryptor(); 21 byte[] resultArray = cTransform.TransformFinalBlock( 22 toEncryptArray, 0, toEncryptArray.Length); 23 tdes.Clear(); 24 return UTF8Encoding.UTF8.GetString(resultArray); 25 } 26 27 private static string Encrypt(string toEncrypt) 28 { 29 byte[] keyArray; 30 byte[] toEncryptArray = UTF8Encoding.UTF8.GetBytes(toEncrypt); 31 MD5CryptoServiceProvider hashmd5 = new MD5CryptoServiceProvider(); 32 keyArray = hashmd5.ComputeHash(UTF8Encoding.UTF8.GetBytes(_MD5)); 33 hashmd5.Clear(); 34 35 TripleDESCryptoServiceProvider tdes = new TripleDESCryptoServiceProvider(); 36 tdes.Key = keyArray; 37 tdes.Mode = CipherMode.ECB; 38 tdes.Padding = PaddingMode.PKCS7; 39 40 ICryptoTransform cTransform = tdes.CreateEncryptor(); 41 byte[] resultArray = 42 cTransform.TransformFinalBlock(toEncryptArray, 0, 43 toEncryptArray.Length); 44 tdes.Clear(); 45 return Convert.ToBase64String(resultArray, 0, resultArray.Length); 46 } 47 48 49 #endregion 50 51 52 #region Cookie 53 54 public static void SaveCookie(string name, string value, int expiredSecond = 0) 55 { 56 var encryptStr = Encrypt(value); 57 var collection = HttpContext.Current.Response.Cookies; 58 collection.Add(new HttpCookie(name) 59 { 60 Value = encryptStr, 61 Expires = expiredSecond > 0 ? System.DateTime.Now.AddSeconds(expiredSecond) : System.DateTime.MaxValue 62 }); 63 } 64 65 public static string GetCookie(string name) 66 { 67 var cookie = HttpContext.Current.Request.Cookies.Get(name); 68 if (cookie == null) 69 { 70 return null; 71 } 72 else 73 { 74 return Decrypt(cookie.Value); 75 } 76 } 77 78 #endregion 79 }
3. Controller新增图片服务。设置图片的Cookie有效期是一分钟
1 public class HomeController : Controller 2 { 3 public ActionResult Index() 4 { 5 return View(); 6 } 7 8 public FileContentResult ImageValidator() 9 { 10 var code = ValidationCodeHelper.CreateValidateCode(); 11 CookieHelper.SaveCookie("PicCode", code, 60); 12 var picByte = ValidationCodeHelper.GetImage(code); 13 return File(picByte, "image/jpeg "); 14 } 15 16 /// <summary> 17 /// 检查图片验证码是否正确 18 /// </summary> 19 /// <param name="code"></param> 20 /// <returns></returns> 21 public ActionResult CheckPicCode(string code) 22 { 23 var cookieCode = CookieHelper.GetCookie("PicCode"); 24 if (string.IsNullOrWhiteSpace(cookieCode)) 25 { 26 return Json("验证码过期",JsonRequestBehavior.AllowGet); 27 } 28 if (code.Trim().ToUpper() == cookieCode.ToUpper()) 29 { 30 return Json("验证码正确",JsonRequestBehavior.AllowGet); 31 } 32 else 33 { 34 return Json("验证码错误",JsonRequestBehavior.AllowGet); 35 } 36 } 37 }
4.前台页面. 通过修改img的src链接,来实现点击刷新图片.
1 <head> 2 <title></title> 3 <script src="https://cdn.static.runoob.com/libs/jquery/1.10.2/jquery.min.js"></script> 4 </head> 5 <body> 6 <input type="text" id="inpCode" /> 7 <img id="picCode" src="/Home/ImageValidator" /> 8 <button id="btnCheck" >校验</button> 9 </body> 10 </html> 11 <script> 12 13 $(function () { 14 15 $("#picCode").on('click', function () { 16 $(this).attr('src', "/Home/ImageValidator?v=" + new Date().getTime()); 17 }) 18 19 $("#btnCheck").on('click', function () { 20 $.ajax({ 21 type: 'get', 22 url: "/Home/CheckPicCode", 23 data: { 24 code:$("#inpCode").val() 25 }, 26 success: function (data) { 27 alert(data); 28 } 29 }) 30 }) 31 32 }) 33 34 </script>
5. 第一版基本代码实现了,点击图片刷新验证码,图片验证码有效期是一分钟.
一分钟内
一分钟后,验证码过期了
6.大家看上面两张图以为验证码OK了。的确。正常网站使用是OK的。因为我们设置了验证码的有效时间是一分钟。那我们看看Fiddler里面的效果。
当我面在Fiddler里面模拟的时候Cookie的过期时间是没有的。也就是如果客户端用户抓取了PicCode的Cookie后。自己构建请求的话。Cookie是不会失效的。
二. 图片验证码改版。
从上面看来 直接设置Cookie的过期时间是不行的。那我们只能在Cookie存储Value上修改了。来看看我们代码实现方式。我们将过期时间的ticks和验证码字符串同时加密存储在了Cookie中。
13 14 /// <summary> 15 /// 构建图片Cookie字符串,code和过期见时间用逗号隔开 16 /// </summary> 17 public static string BuildValidateCodeStorage(string code, int expiredSeconds = 60) 18 { 19 return string.Format("{0},{1}", code, System.DateTime.Now.AddSeconds(expiredSeconds).Ticks); 20 } 21 22 /// <summary> 23 /// 解析图片验证码Cookie 24 /// </summary> 25 public static string AnysisValidateCode(string codeStorage) 26 { 27 if (string.IsNullOrWhiteSpace(codeStorage)) 28 { 29 return null; 30 } 31 else 32 { 33 var vals = codeStorage.Split(','); 34 if (vals.Count() != 2) 35 { 36 return null; 37 } 38 else 39 { 40 long ticks = 0; 41 long.TryParse(vals[1], out ticks); 42 if (ticks > System.DateTime.Now.Ticks) 43 { 44 return vals[0]; 45 } 46 else 47 { 48 return null; 49 } 50 } 51 } 52 }
三.总结
这个是我们暂时想到的图片验证码实现方案。不知道其他博友公司是如何实现的。希望大家也可以分享一下。