欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

MVC使用极验验证制作登录验证码学习笔记7

程序员文章站 2023-08-17 14:58:47
       在之前的项目中,如果有需要使用验证码,基本都是自己用gdi+画图出来,简单好用,但是却也存在了一些...

       在之前的项目中,如果有需要使用验证码,基本都是自己用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:请先完成验证操作。");
 ....
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。