驗證碼 Captcha 之大插件小用
不知何年何月才能完成OADemo啊,總之還是一步一步來吧,這段時間開始着手了,先做登陸。 前段時間研究了一下在CentOS7下安裝Mysql和Memcached服務,並測試了用C#操作,結果還行。
今天做一個簡單的基於Bootstarp的響應式登陸頁面(其實是在網上下的模板),不管是登陸還是注冊吧,都會用到驗證碼,以前是用GDI繪出來的,覺得太丑了,百度的關於.net的驗證碼絕大多數也是用的這種方法,最后試了一下captcha,覺得還挺好看的,所以就試着用用。
nugit控制台install-package captcha到項目就行了,下載好了有個readme.txt文件,照着搞了一下,挺方便的
后台代碼這樣寫的
[HttpPost]
[CaptchaValidation("CaCode", "ExampleCaptcha", "Incorrect CAPTCHA code!")]
public ActionResult ExampleAction(bool captchaValid)
{
if (!captchaValid)
{
return Content("no");
}
else
{
return Content("ok");
}
}
前台這樣子的
<form action="/Example/ExampleAction" method="post">
@{
MvcCaptcha exampleCaptcha = new MvcCaptcha("ExampleCaptcha");
exampleCaptcha.UserInputID = "CaCode";
exampleCaptcha.ImageStyle = BotDetect.ImageStyle.CaughtInTheNet2;
@Html.Captcha(exampleCaptcha)
@*@Html.TextBox("CaCode")*@
<input type="text" id="CaCode" name="CaCode"/>
}
<input type="submit" value="GO" />
</form>
這都是照着readme搞的,當然還有注冊路由的
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.IgnoreRoute("{*botdetect}", new { botdetect = @"(.*)BotDetectCaptcha\.ashx" });
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
就這三步就行了,然后測試一下,先輸個錯的試試,點GO,輸出no,很好,再搞個對的,輸出ok相當可以,再然后,不小心輸錯了還是ok,再然后空的還是ok???
把瀏覽器關了,重新生成再打開試,哪怕第一次是錯的也是輸出ok。另外還有一個問題點圖片就會轉到https://captcha.org/captcha.html?asp.net這個地址。
問題一
出現這個問題按理說應該是好事,說明插件幫我們做了緩存驗證碼的事,並且還設置了過期時間,將部分屬性打印到頁面看看情況
<form action="/Example/ExampleAction" method="post">
@{
MvcCaptcha exampleCaptcha = new MvcCaptcha("ExampleCaptcha");
exampleCaptcha.UserInputID = "CaCode";
exampleCaptcha.ImageStyle = BotDetect.ImageStyle.CaughtInTheNet2;
@Html.Captcha(exampleCaptcha)
@*@Html.TextBox("CaCode")*@
<input type="text" id="CaCode" name="CaCode"/>
<ul>
<li>HelpLinkUrl: @exampleCaptcha.HelpLinkUrl</li>
<li>AddInitScript: @exampleCaptcha.AddInitScript</li>
<li>AdditionalCssClasses: @exampleCaptcha.AdditionalCssClasses</li>
<li>AdditionalInlineCss: @exampleCaptcha.AdditionalInlineCss</li>
<li>AddScriptInclude: @exampleCaptcha.AddScriptInclude</li>
<li>AutoClearInput: @exampleCaptcha.AutoClearInput</li>
<li>AutoFocusInput: @exampleCaptcha.AutoFocusInput</li>
<li>AutoReloadTimeout: @exampleCaptcha.AutoReloadTimeout</li>
<li>CodeLength: @exampleCaptcha.CodeLength</li>
<li>CodeStyle: @exampleCaptcha.CodeStyle</li>
<li>CodeTimeout: @exampleCaptcha.CodeTimeout</li>
<li>HelpLinkEnabled: @exampleCaptcha.HelpLinkEnabled</li>
<li>HelpLinkText: @exampleCaptcha.HelpLinkText</li>
<li>IconsDivWidth: @exampleCaptcha.IconsDivWidth</li>
<li>ReloadIconUrl: @exampleCaptcha.ReloadIconUrl</li>
<li>SoundIconUrl: @exampleCaptcha.SoundIconUrl</li>
<li>ImageStyle: @exampleCaptcha.ImageStyle</li>
<li>Locale: @exampleCaptcha.Locale</li>
<li> @exampleCaptcha.HelpLinkMode</li>
<li> @exampleCaptcha.WebCaptcha.CaptchaImageUrl</li>
<li> @exampleCaptcha.WebCaptcha.CaptchaBase.GetCode("ExampleCaptcha", BotDetect.CodeGenerationPurpose.ImageGeneration).CaptchaCode</li>
</ul>
}
<input type="submit" value="GO" />
</form>

從第一個屬性來看應該是解決問題二的辦法,但是很不幸,設置exampleCaptcha.HelpLinkUrl和HelpLinkText的值無效,結果還是回到了默認值。
還有就是CodeTimeout也是設置無效。在webconfig中
<sessionState mode="InProc" cookieless="AutoDetect" timeout="20" sessionIDManagerType="BotDetect.Web.CustomSessionIdManager, BotDetect" />
設置timeout小於20的值也無效,可能是有個最小值限定吧,這里我還沒有試過超過20的值,暈!
其它的還好吧。
最后一個屬性是我企圖找出當前的驗證碼,依然很不幸,並不是,到現在我還沒辦法得到。之所以想得當驗證碼,其一是我覺得它幫我做的就是在session中緩存了驗證碼,可我並不想用session,如果只是保存在cookie中,那敢情好,我也無需得到驗證碼。
看看點擊刷新按鈕的請求


可以看出有三個GET請求,前兩個是請求刷新和聲音的按鈕,后面一個是請求圖片的。主要看后面一個,看來是有請求cookie的,並且有兩個key。雖然如此,可我還並不肯定到底有沒有用session緩存,關於這個,希望路過的大牛給予指導。
再看看參數,有四個參數,每次請求只有d的值是變化的,這就是跟我們以前用的驗證碼刷新是一個意思啦。這個請求地址正是exampleCaptcha.WebCaptcha.CaptchaImageUrl的屬性值。
看看刷新按鈕最終生成的html是什么樣子的,如下圖,最關鍵的是a標簽的onclick(紅框框起部分),雖然看不到具體方法,但能知道方法的最終結果就是為了完成上面的請求。能理解這個,其它的都可以理解了。

其二我並不喜歡在前端寫過多的C#代碼。觀察了一下后端代碼
[CaptchaValidation("CaCode", "ExampleCaptcha", "Incorrect CAPTCHA code!")]
public ActionResult ExampleAction(bool captchaValid)...
對於Action的參數captchaValid我很不解,當我改變參數名稱后就會報錯,想想應該是CaptchaValidationAttribute中得來的吧,覺得用這個Attribute限制太多,不夠靈活。最終我去掉了Attribute,用MvcCaptcha類的靜態方法 MvcCaptcha.Validate(string,string,string),這個重載需要三個參數一個是captcha的Id,一個是用戶輸入值,另一個是validtingInstanceId
對於最后一個參數我開始摸不着北,后面在html源碼中找到個hidden的input,只是感覺像是它,因為它有name屬性,憑着感覺試了,沒想還真行

那么就把后台給改了試試,其實最后個參數的值就是exampleCaptcha.CurrentInstanceId這個屬性值(有時我自己覺得我有點傻的可愛),這樣一來問題一就解決了部分問題,可以不用在前端寫那么多C#代碼了,並且每次登陸頁面都要輸入對的驗證碼才能ok。
public ActionResult ExampleAction(string CaCode, string BDC_VCID_ExampleCaptcha)
{
string instaneId = CustomCaptcha.CurrentInstanceId;
bool captchaValid= MvcCaptcha.Validate("ExampleCaptcha", CaCode,BDC_VCID_ExampleCaptcha);
if (captchaValid)
return Content("ok");
return Content("no");
}
問題二
解決問題二我用的辦法很粗暴,我后來重做了一個MVC項目,做了一個完整的登陸頁面,那個刷新和聲音按鈕我不好布局,直接給隱藏了,既然它粗暴的讓改屬性值不能改變驗證圖片的點擊鏈接,我又不能說讓用戶點圖片進入一個外國網站,那么我也只能粗暴通過jquery來解決問題,我想的辦法就是把前面關於刷新按鈕的a標簽的onclick給圖片中a標簽的onclick,簡單粗暴。
雖然兩個問題並沒有完美解決,但感覺自己還在挺努力的。所以最后把修改的全部代碼貼上吧
最終效果

前端
View Code
后端專門寫了一個類,有一個靜態屬性,用於指定當前驗證碼的實例Id,一個靜態方法,用於自定義captcha
using BotDetect.Web.Mvc;
namespace OADemo.MVCApp
{
public class CustomCaptcha
{
/// <summary>
/// 當前驗證碼的實例Id
/// </summary>
public static string CurrentInstanceId { get { return currentInstanceId; } }
private static string currentInstanceId;
public static MvcCaptcha GetCaptcha(string captchaId)
{
MvcCaptcha exampleCaptcha = new MvcCaptcha(captchaId);
exampleCaptcha.ImageStyle = BotDetect.ImageStyle.CaughtInTheNet2;
exampleCaptcha.ReloadEnabled = false;
exampleCaptcha.SoundEnabled = false;
currentInstanceId = exampleCaptcha.CurrentInstanceId;
return exampleCaptcha;
}
/// <summary>
/// 獲取自定義的驗證碼樣式
/// </summary>
/// <param name="captchaId"></param>
/// <param name="width">指定驗證碼圖片的寬度</param>
/// <param name="height">指定驗證碼圖片的高度</param>
/// <param name="codeLength">指定驗證碼的個數</param>
/// <returns></returns>
public static MvcCaptcha GetCaptcha(string captchaId,int width,int height,int codeLength)
{
MvcCaptcha exampleCaptcha = new MvcCaptcha(captchaId);
exampleCaptcha.ImageStyle = BotDetect.ImageStyle.CaughtInTheNet2;//我個人喜歡的風格
exampleCaptcha.ReloadEnabled = false;//去掉刷新的按鈕
exampleCaptcha.SoundEnabled = false;//去掉聲音播放按鈕
exampleCaptcha.CodeLength = codeLength;//指定驗證碼的長度
exampleCaptcha.ImageSize = new System.Drawing.Size(width,height);//指定圖片的大小
currentInstanceId = exampleCaptcha.CurrentInstanceId;//當前實例的id
return exampleCaptcha;
}
}
}
后端控制器代碼
Controller Code
好吧,后端控制器代碼應該是這樣才對的
using System.Web.Mvc;
namespace OADemo.MVCApp.Controllers
{
public class LoginController : Controller
{
// GET: Login
public ActionResult Index()
{
return View();
}
public ActionResult Account(string CaptchaCode, string username, string password)
{
string instaneId = CustomCaptcha.CurrentInstanceId;
bool captchaValid= MvcCaptcha.Validate("ExampleCaptcha", CaptchaCode,instaneId);
if (captchaValid)
return Content("ok");
return Content("no");
}
}
}
目前就寫到這里了,當然后面的驗證還沒寫,還只是在測試captcha階段,所以想說要源碼的同志,就不要費力了,因為這里忘記密碼和注冊都沒有寫的。這一篇完全只是寫的我目前對captcha的研究,也許在眾多的MvcCaptcha類的事件中,才是真正有效解決問題的辦法,只是我沒有英文水平,加上現在時間太晚,眼睛和腦袋受不了,暫告一段落,事件這么多呢,搞懂了,webconfig配置都可以自定義
public static event EventHandler<GeneratedCaptchaCodeEventArgs> GeneratedCaptchaCode;
public static event EventHandler<GeneratedCaptchaImageEventArgs> GeneratedCaptchaImage;
public static event EventHandler<GeneratedCaptchaSoundEventArgs> GeneratedCaptchaSound;
public static event EventHandler<GeneratingCaptchaCodeEventArgs> GeneratingCaptchaCode;
public static event EventHandler<GeneratingCaptchaImageEventArgs> GeneratingCaptchaImage;
public static event EventHandler<GeneratingCaptchaSoundEventArgs> GeneratingCaptchaSound;
public static event EventHandler<InitializedWebCaptchaEventArgs> InitializedWebCaptcha;
public static event EventHandler<ValidatedUserInputEventArgs> ValidatedUserInput;
public static event EventHandler<ValidatingUserInputEventArgs> ValidatingUserInput;

