MVC使用极验验证制作登录验证码学习笔记7
在之前的项目中,如果有需要使用验证码,基本都是自己用gdi+画图出来,简单好用,但是却也存在了一些小问题,首先若较少干扰线,则安全性不是很高,验证码容易被机器识别,若多画太多干扰线条,机器人识别率下降的同时,人眼的识别率也同步下降(震惊哭)。更为重要的是,gdi+绘制的验证码一般来说也不会很美观,如果做一个炫酷的登陆界面却配了这样一个验证码,画风诡异,丑到极致。
再后来浏览网页的过程中,发现很多很多网站项目中都使用了一种叫极验验证的验证码,采用移动滑块的方式进行验证,方便美观。而一番搜索之后了解到,官方提供的免费版也足以应付我手头的大多数项目了,不禁想把在mvc学习过程中试着使用极验验证来作为登录的验证码。
极验官方提供了c#的sdk和demo供开发者参考,不过是webform版本的,可读性不是很高,而现在使用webform进行网站开发的也基本消失了,我将在官方webform代码的基础上,将其用在asp.net mvc程序中。
注册极验
到极验官网注册账号之后进入后台管理界面,点击添加验证
添加后我们可以得到id和key
完成验证逻辑
1. 首先我们需要引入官方的geetestlib类
using system; using system.collections; using system.collections.generic; using system.linq; using system.text; using system.security.cryptography; using system.net; using system.io; namespace pms.webapp.models { /// <summary> /// geetestlib 极验验证c# sdk基本库 /// </summary> public class geetestlib { /// <summary> /// sdk版本号 /// </summary> public const string version = "3.2.0"; /// <summary> /// sdk开发语言 /// </summary> public const string sdklang = "csharp"; /// <summary> /// 极验验证api url /// </summary> protected const string apiurl = "http://api.geetest.com"; /// <summary> /// register url /// </summary> protected const string registerurl = "/register.php"; /// <summary> /// validate url /// </summary> protected const string validateurl = "/validate.php"; /// <summary> /// 极验验证api服务状态session key /// </summary> public const string gtserverstatussessionkey = "gt_server_status"; /// <summary> /// 极验验证二次验证表单数据 chllenge /// </summary> public const string fngeetestchallenge = "geetest_challenge"; /// <summary> /// 极验验证二次验证表单数据 validate /// </summary> public const string fngeetestvalidate = "geetest_validate"; /// <summary> /// 极验验证二次验证表单数据 seccode /// </summary> public const string fngeetestseccode = "geetest_seccode"; private string userid = ""; private string responsestr = ""; private string captchaid = ""; private string privatekey = ""; /// <summary> /// 验证成功结果字符串 /// </summary> public const int successresult = 1; /// <summary> /// 证结失败验果字符串 /// </summary> public const int failresult = 0; /// <summary> /// 判定为机器人结果字符串 /// </summary> public const string forbiddenresult = "forbidden"; /// <summary> /// geetestlib构造函数 /// </summary> /// <param name="publickey">极验验证公钥</param> /// <param name="privatekey">极验验证私钥</param> public geetestlib(string publickey, string privatekey) { this.privatekey = privatekey; this.captchaid = publickey; } private int getrandomnum() { random rand =new random(); int randres = rand.next(100); return randres; } /// <summary> /// 验证初始化预处理 /// </summary> /// <returns>初始化结果</returns> public byte preprocess() { if (this.captchaid == null) { console.writeline("publickey is null!"); } else { string challenge = this.registerchallenge(); if (challenge.length == 32) { this.getsuccesspreprocessres(challenge); return 1; } else { this.getfailpreprocessres(); console.writeline("server regist challenge failed!"); } } return 0; } public byte preprocess(string userid) { if (this.captchaid == null) { console.writeline("publickey is null!"); } else { this.userid = userid; string challenge = this.registerchallenge(); if (challenge.length == 32) { this.getsuccesspreprocessres(challenge); return 1; } else { this.getfailpreprocessres(); console.writeline("server regist challenge failed!"); } } return 0; } public string getresponsestr() { return this.responsestr; } /// <summary> /// 预处理失败后的返回格式串 /// </summary> private void getfailpreprocessres() { int rand1 = this.getrandomnum(); int rand2 = this.getrandomnum(); string md5str1 = this.md5encode(rand1 + ""); string md5str2 = this.md5encode(rand2 + ""); string challenge = md5str1 + md5str2.substring(0, 2); this.responsestr = "{" + string.format( "\"success\":{0},\"gt\":\"{1}\",\"challenge\":\"{2}\"", 0, this.captchaid, challenge) + "}"; } /// <summary> /// 预处理成功后的标准串 /// </summary> private void getsuccesspreprocessres(string challenge) { challenge = this.md5encode(challenge + this.privatekey); this.responsestr ="{" + string.format( "\"success\":{0},\"gt\":\"{1}\",\"challenge\":\"{2}\"", 1, this.captchaid, challenge) + "}"; } /// <summary> /// failback模式的验证方式 /// </summary> /// <param name="challenge">failback模式下用于与validate一起解码答案, 判断验证是否正确</param> /// <param name="validate">failback模式下用于与challenge一起解码答案, 判断验证是否正确</param> /// <param name="seccode">failback模式下,其实是个没用的参数</param> /// <returns>验证结果</returns> public int failbackvalidaterequest(string challenge, string validate, string seccode) { if (!this.requestislegal(challenge, validate, seccode)) return geetestlib.failresult; string[] validatestr = validate.split('_'); string encodeans = validatestr[0]; string encodefullbgimgindex = validatestr[1]; string encodeimggrpindex = validatestr[2]; int decodeans = this.decoderesponse(challenge, encodeans); int decodefullbgimgindex = this.decoderesponse(challenge, encodefullbgimgindex); int decodeimggrpindex = this.decoderesponse(challenge, encodeimggrpindex); int validateresult = this.validatefailimage(decodeans, decodefullbgimgindex, decodeimggrpindex); return validateresult; } private int validatefailimage(int ans, int full_bg_index, int img_grp_index) { const int thread = 3; string full_bg_name = this.md5encode(full_bg_index + "").substring(0, 10); string bg_name = md5encode(img_grp_index + "").substring(10, 10); string answer_decode = ""; for (int i = 0;i < 9; i++) { if (i % 2 == 0) answer_decode += full_bg_name.elementat(i); else if (i % 2 == 1) answer_decode += bg_name.elementat(i); } string x_decode = answer_decode.substring(4); int x_int = convert.toint32(x_decode, 16); int result = x_int % 200; if (result < 40) result = 40; if (math.abs(ans - result) < thread) return geetestlib.successresult; else return geetestlib.failresult; } private boolean requestislegal(string challenge, string validate, string seccode) { if (challenge.equals(string.empty) || validate.equals(string.empty) || seccode.equals(string.empty)) return false; return true; } /// <summary> /// 向gt-server进行二次验证 /// </summary> /// <param name="challenge">本次验证会话的唯一标识</param> /// <param name="validate">拖动完成后server端返回的验证结果标识字符串</param> /// <param name="seccode">验证结果的校验码,如果gt-server返回的不与这个值相等则表明验证失败</param> /// <returns>二次验证结果</returns> public int enhencedvalidaterequest(string challenge, string validate, string seccode) { if (!this.requestislegal(challenge, validate, seccode)) return geetestlib.failresult; if (validate.length > 0 && checkresultbyprivate(challenge, validate)) { string query = "seccode=" + seccode + "&sdk=csharp_" + geetestlib.version; string response = ""; try { response = postvalidate(query); } catch (exception e) { console.writeline(e); } if (response.equals(md5encode(seccode))) { return geetestlib.successresult; } } return geetestlib.failresult; } public int enhencedvalidaterequest(string challenge, string validate, string seccode, string userid) { if (!this.requestislegal(challenge, validate, seccode)) return geetestlib.failresult; if (validate.length > 0 && checkresultbyprivate(challenge, validate)) { string query = "seccode=" + seccode + "&user_id=" + userid + "&sdk=csharp_" + geetestlib.version; string response = ""; try { response = postvalidate(query); } catch (exception e) { console.writeline(e); } if (response.equals(md5encode(seccode))) { return geetestlib.successresult; } } return geetestlib.failresult; } private string readcontentfromget(string url) { try { httpwebrequest request = (httpwebrequest)webrequest.create(url); request.timeout = 20000; httpwebresponse response = (httpwebresponse)request.getresponse(); stream myresponsestream = response.getresponsestream(); streamreader mystreamreader = new streamreader(myresponsestream, encoding.getencoding("utf-8")); string retstring = mystreamreader.readtoend(); mystreamreader.close(); myresponsestream.close(); return retstring; } catch { return ""; } } private string registerchallenge() { string url = ""; if (string.empty.equals(this.userid)) { url = string.format("{0}{1}?gt={2}", geetestlib.apiurl, geetestlib.registerurl, this.captchaid); } else { url = string.format("{0}{1}?gt={2}&user_id={3}", geetestlib.apiurl, geetestlib.registerurl, this.captchaid, this.userid); } string retstring = this.readcontentfromget(url); return retstring; } private boolean checkresultbyprivate(string origin, string validate) { string encodestr = md5encode(privatekey + "geetest" + origin); return validate.equals(encodestr); } private string postvalidate(string data) { string url = string.format("{0}{1}", geetestlib.apiurl, geetestlib.validateurl); httpwebrequest request = (httpwebrequest)webrequest.create(url); request.method = "post"; request.contenttype = "application/x-www-form-urlencoded"; request.contentlength = encoding.utf8.getbytecount(data); // 发送数据 stream myrequeststream = request.getrequeststream(); byte[] requestbytes = system.text.encoding.ascii.getbytes(data); myrequeststream.write(requestbytes, 0, requestbytes.length); myrequeststream.close(); httpwebresponse response = (httpwebresponse)request.getresponse(); // 读取返回信息 stream myresponsestream = response.getresponsestream(); streamreader mystreamreader = new streamreader(myresponsestream, encoding.getencoding("utf-8")); string retstring = mystreamreader.readtoend(); mystreamreader.close(); myresponsestream.close(); return retstring; } private int decoderandbase(string challenge) { string basestr = challenge.substring(32, 2); list<int> templist = new list<int>(); for(int i = 0; i < basestr.length; i++) { int tempascii = (int)basestr[i]; templist.add((tempascii > 57) ? (tempascii - 87) : (tempascii - 48)); } int result = templist.elementat(0) * 36 + templist.elementat(1); return result; } private int decoderesponse(string challenge, string str) { if (str.length>100) return 0; int[] shuzi = new int[] { 1, 2, 5, 10, 50}; string chongfu = ""; hashtable key = new hashtable(); int count = 0; for (int i=0;i<challenge.length;i++) { string item = challenge.elementat(i) + ""; if (chongfu.contains(item)) continue; else { int value = shuzi[count % 5]; chongfu += item; count++; key.add(item, value); } } int res = 0; for (int i = 0; i < str.length; i++) res += (int)key[str[i]+""]; res = res - this.decoderandbase(challenge); return res; } private string md5encode(string plaintext) { md5cryptoserviceprovider md5 = new md5cryptoserviceprovider(); string t2 = bitconverter.tostring(md5.computehash(utf8encoding.default.getbytes(plaintext))); t2 = t2.replace("-", ""); t2 = t2.tolower(); return t2; } } }
2. 获取验证码
引入jquery库
<script src="~/content/plugins/jquery/jquery-1.8.2.min.js"></script>
添加用于放置验证码的div(需要放到form表单中)
<div id="geetest-container">
</div>
添加js代码用于获取验证码
<script> window.addeventlistener('load', processgeetest); function processgeetest() { $.ajax({ // 获取id,challenge,success(是否启用failback) url: "/login/geektest", type: "get", datatype: "json", // 使用jsonp格式 success: function (data) { // 使用initgeetest接口 // 参数1:配置参数,与创建geetest实例时接受的参数一致 // 参数2:回调,回调的第一个参数验证码对象,之后可以使用它做appendto之类的事件 initgeetest({ gt: data.gt, challenge: data.challenge, product: "float", // 产品形式 offline: !data.success }, handler); } }); } var handler = function (captchaobj) { // 将验证码加到id为captcha的元素里 captchaobj.appendto("#geetest-container"); captchaobj.onsuccess = function (e) { console.log(e); } }; </script>
processgeetest方法中我们异步请求的地址“/login/geektest”就是获取验证码是后台需要执行的方法
public actionresult geektest() { return content(getcaptcha(),"application/json"); } private string getcaptcha() { var geetest = new geetestlib("3594e0d834df77cedc7351a02b5b06a4", "b961c8081ce88af7e32a3f45d00dff84"); var gtserverstatus = geetest.preprocess(); session[geetestlib.gtserverstatussessionkey] = gtserverstatus; return geetest.getresponsestr(); }
3. 校验验证码
注意,当提交form表单时,会将三个和极验有关的参数传到后台方法(geetest_challenge、geetest_validate、geetest_seccode),若验证码未验证成功,则参数为空值。
后台验证方法为:
private bool checkgeetestresult() { var geetest = new geetestlib("3594e0d834df77cedc7351a02b5b06a4", "b961c8081ce88af7e32a3f45d00dff84 "); var gtserverstatuscode = (byte)session[geetestlib.gtserverstatussessionkey]; var userid = (string)session["userid"]; var challenge = request.form.get(geetestlib.fngeetestchallenge); var validate = request.form.get(geetestlib.fngeetestvalidate); var seccode = request.form.get(geetestlib.fngeetestseccode); var result = gtserverstatuscode == 1 ? geetest.enhencedvalidaterequest(challenge, validate, seccode, userid) : geetest.failbackvalidaterequest(challenge, validate, seccode); return result == 1; }
我们可以在表单中判断验证码是否成功校验:
public actionresult login() { if (!checkgeetestresult()) return content("no:请先完成验证操作。"); .... }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
上一篇: iOS自定义水平滚动条、进度条