이전 프로젝트에서는 인증 코드를 사용해야 할 경우 기본적으로 GDI+를 사용하여 직접 작성했습니다. 간단하고 사용하기 쉽지만 우선 작은 문제가 있습니다. 간섭선이 많이 그려지면 보안성이 좋지 않아 기계가 인증코드를 쉽게 인식하게 되며, 간섭선을 너무 많이 그리면 로봇의 인식률도 떨어지고 사람의 눈의 인식률도 떨어지게 됩니다. 동시에 (충격과 울음). 더 중요한 것은 GDI+에서 작성한 인증 코드가 일반적으로 그다지 아름답지 않다는 점입니다. 멋진 로그인 인터페이스를 만들었는데 그런 인증 코드가 있으면 그리기 스타일이 이상하고 극도로 추악해질 것입니다.
나중에 웹서핑을 하다가 많은 웹사이트 프로젝트에서 Ji Verification이라는 인증코드를 사용하고 있다는 것을 알게 되었는데, 슬라이더를 움직여 인증하는 것이 편리하고 아름답습니다. 몇 번 검색한 결과, 제가 진행 중인 대부분의 프로젝트에 공식 무료 버전이면 충분하다는 것을 알게 되었습니다. MVC 학습 과정에서 Jiexian 인증을 로그인 인증 코드로 사용해 보고 싶었습니다.
Jiexian은 개발자가 참고할 수 있도록 C# SDK 및 Demo를 공식적으로 제공합니다. 하지만 요즘에는 웹사이트 개발을 위한 Webform의 사용이 기본적으로 사라졌습니다. 공식 Webform 코드를 ASP.NET MVC 프로그램에서 사용하세요.
JiXian 등록
JiXian 공식 웹사이트로 이동하여 계정을 등록하고 백엔드 관리 인터페이스에 들어간 후 확인 추가를 클릭하세요
후 추가하면 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 라이브러리 소개