在服务器往返和随后重新生成验证码之后验证验证码

时间:2010-02-02 23:39:52

标签: asp.net-mvc

根据Sanderson的书籍 Pro ASP.NET MVC Framework ,我在表单提交中实现了CAPTCHA。

使用以下内容生成视图字段:

<%= Html.Captcha("testCaptcha")%>
<%= Html.TextBox("attemptCaptcha")%>

VerifyAndExpireSolution助手无法正常工作,因为他的解决方案已实施。

我正在添加验证,当它失败时,我会添加一个ModelState错误消息并将用户发送回视图中所述的视图:

return ModelState.IsValid ? View("Completed", appt) : View();

但是,这样做会生成一个新的GUID,用于生成新的CAPTCHA文本。

但问题是,CAPTCHA隐藏字段值和CAPTCHA图像网址都保留原始 GUID。所以,你永远无法输入正确的价值。你基本上只有一次机会才能做到正确。

我是所有这一切的新手,但它与保留第一页加载的值的视图有关。

使用以下代码生成验证码。

public static string Captcha(this HtmlHelper html, string name)
{
    // Pick a GUID to represent this challenge
    string challengeGuid = Guid.NewGuid().ToString();
    // Generate and store a random solution text
    var session = html.ViewContext.HttpContext.Session;
    session[SessionKeyPrefix + challengeGuid] = MakeRandomSolution();

    // Render an <IMG> tag for the distorted text,
    // plus a hidden field to contain the challenge GUID
    var urlHelper = new UrlHelper(html.ViewContext.RequestContext);
    string url = urlHelper.Action("Render", "CaptchaImage", new{challengeGuid});
    return string.Format(ImgFormat, url) + html.Hidden(name, challengeGuid);
}

然后我尝试用以下方法验证它:

public static bool VerifyAndExpireSolution(HttpContextBase context,
                                       string challengeGuid,
                                       string attemptedSolution)
{
    // Immediately remove the solution from Session to prevent replay attacks
    string solution = (string)context.Session[SessionKeyPrefix + challengeGuid];
    context.Session.Remove(SessionKeyPrefix + challengeGuid);

    return ((solution != null) && (attemptedSolution == solution));
}

如何使用guid重建目标字段名称?那么,每个字段都是唯一的,并且不会保留以前的表单代的值?

或者我只需要一个不同的CAPTCHA实现?

2 个答案:

答案 0 :(得分:1)

我使用Sanderson的书中的验证码示例遇到了同样的问题。问题是页面被浏览器缓存,并且在验证码测试失败后不会刷新。因此它总是显示相同的图像,即使已经生成并存储了新的验证码以进行测试。

一种解决方案是在尝试失败后重新加载时强制浏览器刷新页面;如果你只返回View(),就不会发生这种情况。您可以使用RedirectToAction(“SubmitEssay”)执行此操作,该操作将触及接受HttpVerbs.Get的操作方法。

当然,您无法使用ViewData通知您的用户错误,但您可以将其包含在查询字符串中,然后只需检查查询字符串以显示您的消息。

所以,按照这本书的例子,

if (!CaptchaHelper.VerifyAndExpireSolution(HttpContext, captcha, captchaAttempt) 
{
    RedirectToAction("SubmitEssay", new { fail = 1 });
}

然后检查QueryString集合是否包含“fail”以传递错误消息。

答案 1 :(得分:0)

所以,我决定实施reCaptcha。我也同样定制了我的观点:

<div id="recaptcha_image"></div>&nbsp;
     <a href="#" onclick="Recaptcha.reload();">
            generate a new image
     </a><br />
<input type="text" name="recaptcha_response_field" 
           id="recaptcha_response_field" />
           &nbsp;<%= Html.ValidationMessage("attemptCaptcha")%>
<script type="text/javascript" 
     src="http://api.recaptcha.net/challenge?k=[my public key]"></script>

这会创建两个验证码 - 一个在我的图像容器中,另一个由脚本创建。所以,我添加了css来隐藏自动生成的:

<style type="text/css">
    #recaptcha_widget_div {display:none;}
</style>

然后,在我的控制器中,我只需要测试captchaValid:

[CaptchaValidator]
[AcceptVerbs(HttpVerbs.Post)]
public ViewResult SubmitEssay(Essay essay, bool acceptsTerms, bool captchaValid)
{
    if (!acceptsTerms)
        ModelState.AddModelError("acceptsTerms", 
                     "You must accept the terms and conditions.");
    else
    {
       try
       {
            // save/validate the essay
            var errors = essay.GetRuleViolations(captchaValid);
            if (errors.Count > 0)
                throw new RuleException(errors);

        }
        catch (RuleException ex)
        {
            ex.CopyToModelState(ModelState, "essay");
        }
    }
    return ModelState.IsValid ? View("Completed", essay) : View();
}

public NameValueCollection GetRuleViolations(bool captchaValid)
{
    var errors = new NameValueCollection();
    if (!captchaValid)
        errors.Add("attemptCaptcha", 
             "Please enter the correct verification text before submitting.");
    // continue with other fields....
}

所有这些假设您已经实现了Action Filter属性和视图助手,详见recaptcha.net:

public class CaptchaValidatorAttribute : ActionFilterAttribute
{
    private const string CHALLENGE_FIELD_KEY = "recaptcha_challenge_field";
    private const string RESPONSE_FIELD_KEY = "recaptcha_response_field";

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        var captchaChallengeValue = 
             filterContext.HttpContext.Request.Form[CHALLENGE_FIELD_KEY];
        var captchaResponseValue = 
             filterContext.HttpContext.Request.Form[RESPONSE_FIELD_KEY];
        var captchaValidtor = new Recaptcha.RecaptchaValidator
          {
              PrivateKey = "[my private key]",
              RemoteIP = filterContext.HttpContext.Request.UserHostAddress,
              Challenge = captchaChallengeValue,
              Response = captchaResponseValue
          };

        var recaptchaResponse = captchaValidtor.Validate();

    // this will push the result value into a parameter in our Action
        filterContext.ActionParameters["captchaValid"] = recaptchaResponse.IsValid;

        base.OnActionExecuting(filterContext);
    }
}

html helper:

public static class Captcha
{
    public static string GenerateCaptcha( this HtmlHelper helper )
    {  
    var captchaControl = new Recaptcha.RecaptchaControl
        {
            ID = "recaptcha",
            Theme = "clean",
            PublicKey = "[my public key]",
            PrivateKey = "[ my private key ]"
        };
    var htmlWriter = new HtmlTextWriter( new StringWriter() );
        captchaControl.RenderControl(htmlWriter);
    return htmlWriter.InnerWriter.ToString();
    }
}

希望这可以帮助那些在书中坚持实施的人。

相关问题