• [MVC学习笔记]7.使用极验验证来制作更高逼格的验证码


           在之前的项目中,如果有需要使用验证码,基本都是自己用GDI+画图出来,简单好用,但是却也存在了一些小问题,首先若较少干扰线,则安全性不是很高,验证码容易被机器识别,若多画太多干扰线条,机器人识别率下降的同时,人眼的识别率也同步下降(震惊哭)。更为重要的是,GDI+绘制的验证码一般来说也不会很美观,如果做一个炫酷的登陆界面却配了这样一个验证码,画风诡异,丑到极致。

          再后来浏览网页的过程中,发现很多很多网站项目中都使用了一种叫极验验证的验证码,采用移动滑块的方式进行验证,方便美观。而一番搜索之后了解到,官方提供的免费版也足以应付我手头的大多数项目了,不禁想把在MVC学习过程中试着使用极验验证来作为登录的验证码。

          极验官方提供了C#的SDK和Demo供开发者参考,不过是Webform版本的,可读性不是很高,而现在使用Webform进行网站开发的也基本消失了,我将在官方Webform代码的基础上,将其用在ASP.NET MVC程序中。

    注册极验

    到极验官网注册账号之后进入后台管理界面,点击添加验证

    image

    添加后我们可以得到ID和KEY

    image

    完成验证逻辑

    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:请先完成验证操作。");
        ....
    }
  • 相关阅读:
    Android基础知识
    使用Android Studio和Genymotion模拟器搭建Andriod开发环境
    【原】Java开发环境搭建
    【转】JavaScript顶级对象参考模型
    【转】在.Net中关于AOP的实现
    【转】各版本IIS下ASP.net请求处理过程区别
    【转】我们应该如何去了解JavaScript引擎的工作原理
    Linux系统编程(27)——线程控制
    Linux系统编程(26)——守护进程
    Linux系统编程(25)——终端
  • 原文地址:https://www.cnblogs.com/WayneShao/p/5890221.html
Copyright © 2020-2023  润新知