在之前的項目中,如果有需要使用驗證碼,基本都是自己用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:請先完成驗證操作。");
....
}
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。