首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Google reCAPTCHA验证器:迭代2

Google reCAPTCHA验证器:迭代2
EN

Code Review用户
提问于 2015-08-11 15:52:23
回答 2查看 1.4K关注 0票数 6

这是我(不到一小时前)关于我的Google reCAPTCHA C#实现的另一篇文章的后续文章:Google reCAPTCHA Validator

这增加了对错误消息的支持,以便您可以确定reCAPTCHA请求失败的原因(如果返回了错误)。

如果在将ReCaptchaLocationInclude设置为非空列表之前使用ExtraClasses,也会修复错误。

我为错误添加了一个枚举:

代码语言:javascript
复制
/// <summary>
/// Indicates errors that could be returned by the reCAPTCHA API.
/// </summary>
/// <remarks>
/// See: https://developers.google.com/recaptcha/docs/verify
/// </remarks>
[Flags]
public enum ReCaptchaErrors
{
    /// <summary>
    /// No errors occurred.
    /// </summary>
    None = 0x00,
    /// <summary>
    /// The secret parameter is missing.
    /// </summary>
    MissingInputSecret = 0x01,
    /// <summary>
    /// The secret parameter is invalid or malformed.
    /// </summary>
    InvalidInputSecret = 0x02,
    /// <summary>
    /// The response parameter is missing.
    /// </summary>
    MissingInputResponse = 0x04,
    /// <summary>
    /// The response parameter is invalid or malformed.
    /// </summary>
    InvalidInputResponse = 0x08,
}

用于reCAPTCHA响应的类:

代码语言:javascript
复制
/// <summary>
/// This class is used by the <see cref="ReCaptchaValidator"/> to return a proper response to a reCAPTCHA validation request.
/// </summary>
public class ReCaptchaResponse
{
    private bool _Success;
    private ReCaptchaErrors _Errors;

    /// <summary>
    /// Returns a value indicating if the <see cref="ReCaptchaValidator"/> succeeded in validating the reCAPTCHA response or not.
    /// </summary>
    public bool Success { get { return _Success; } }

    /// <summary>
    /// Returns any <see cref="ReCaptchaErrors"/> that occurred during the reCAPTCHA response validation.
    /// </summary>
    public ReCaptchaErrors Errors { get { return _Errors; } }

    /// <summary>
    /// Creates a new <see cref="ReCaptchaResponse"/> from the specified JSON string.
    /// </summary>
    /// <param name="jsonResponse">The JSON string to transform.</param>
    public ReCaptchaResponse(string jsonResponse)
    {
        JavaScriptSerializer jss = new JavaScriptSerializer();
        dynamic deserializedJson = jss.DeserializeObject(jsonResponse);

        _Success = deserializedJson["success"];
        _Errors = ReCaptchaErrors.None;

        if (deserializedJson.ContainsKey("error-codes"))
        {
            foreach (string error in deserializedJson["error-codes"])
            {
                // Our `ReCaptchaErrors` enum contains the exact same strings as the returned `error` text would be, with the following transformations:
                // 1. The words are transformed to PascalCase;
                // 2. The dashes are stripped;
                string[] errorWords = error.Split('-');

                string errorEnumName = "";
                foreach (string errorWord in errorWords)
                    errorEnumName += errorWord[0].ToString().ToUpper() + errorWord.Substring(1);

                _Errors = _Errors | (ReCaptchaErrors)Enum.Parse(typeof(ReCaptchaErrors), errorEnumName);
            }
        }
    }
}

并更新了reCAPTCHA验证器:

代码语言:javascript
复制
/// <summary>
/// This class provides the ability to easily implement Google's reCAPTCHA.
/// </summary>
/// <remarks>
/// See: https://www.google.com/recaptcha/intro/index.html
/// </remarks>
public class ReCaptchaValidator
{
    private const string _HeadScriptInclude = "<script src='https://www.google.com/recaptcha/api.js'></script>";
    private const string _BodyDivInclude = "<div class=\"g-recaptcha %EXTRACLASSES%\" data-sitekey=\"%SITEKEY%\"></div>";
    private const string _ReCaptchaFormCode = "g-recaptcha-response";

    private readonly string _ReCaptchaSecret;
    private readonly string _ReCaptchaSiteKey;
    private readonly List<string> _ExtraClasses = new List<string>();

    /// <summary>
    /// Returns the script to be included in the <code><head></code> of the page.
    /// </summary>
    public string HeadScriptInclude { get { return _HeadScriptInclude; } }

    /// <summary>
    /// Use this to get or set any extra classes that should be added to the <code><div></code> that is created by the <see cref="BodyDivInclude"/>.
    /// </summary>
    public List<string> ExtraClasses { get { return _ExtraClasses; } }

    /// <summary>
    /// Returns the <code><div></code> that should be inserted in the HTML where the reCAPTCHA should go.
    /// </summary>
    /// <remarks>
    /// I'm still not sure if this should be a method or not.
    /// </remarks>
    public string BodyDivInclude { get { return _BodyDivInclude.Replace("%SITEKEY%", _ReCaptchaSiteKey).Replace("%EXTRACLASSES%", string.Join(" ", ExtraClasses)); } }

    /// <summary>
    /// Creates a new instance of the <see cref="ReCaptchaValidator"/>.
    /// </summary>
    /// <param name="reCaptchaSecret">The reCAPTCHA secret.</param>
    /// <param name="reCaptchaSiteKey">The reCAPTCHA site key.</param>
    public ReCaptchaValidator(string reCaptchaSecret, string reCaptchaSiteKey)
    {
        _ReCaptchaSecret = reCaptchaSecret;
        _ReCaptchaSiteKey = reCaptchaSiteKey;
    }

    /// <summary>
    /// Determines if the reCAPTCHA response in a <code>NameValueCollection</code> passed validation.
    /// </summary>
    /// <param name="form">The <code>Request.Form</code> to validate.</param>
    /// <returns>A <see cref="ReCaptchaResponse"/> value indicating the response of the verification.</returns>
    public ReCaptchaResponse Validate(NameValueCollection form)
    {
        string reCaptchaSecret = _ReCaptchaSecret;
        string reCaptchaResponse = form[_ReCaptchaFormCode];

        using (WebClient client = new WebClient())
        {
            // TODO: send user's IP optionally with reCAPTCHA information.
            byte[] response = client.UploadValues("https://www.google.com/recaptcha/api/siteverify",
                                                    new NameValueCollection() { { "secret", reCaptchaSecret }, { "response", reCaptchaResponse } });

            string reCaptchaResult = System.Text.Encoding.UTF8.GetString(response);

            return new ReCaptchaResponse(reCaptchaResult);
        }
    }
}

对于更新的ReCaptchaValidator,我也将ReCaptchaLocationInclude属性更改为BodyDivInclude,以使名称更有意义。我还使ExtraClasses成为一个只获取的List,这样您就不会意外地给它分配一个新的列表。(您只需先使用ReCaptchaValidator.ExtraClasses.Clear(),然后添加新类即可。)

用法:

代码语言:javascript
复制
string reCaptchaSecret = "";
string reCaptchaSiteKey = "";

protected void Page_Load(object sender, EventArgs e)
{
    ReCaptchaValidator rcv = new ReCaptchaValidator(reCaptchaSecret, reCaptchaSiteKey);
    reCaptchaBodyCode.Text = rcv.BodyDivInclude;
    reCaptchaHeadCode.Text = rcv.HeadScriptInclude;
}

protected void CreateUser_Click(object sender, EventArgs e)
{
    ReCaptchaValidator rcv = new ReCaptchaValidator(reCaptchaSecret, reCaptchaSiteKey);
    ReCaptchaResponse reCaptchaResponse = rcv.Validate(Request.Form);

    if (reCaptchaResponse.Success)
    {
        // Please do not comment on code within this block, it is provided as-is by Microsoft and I will not change it unless it is broken (and it is not).

        var manager = Context.GetOwinContext().GetUserManager<ApplicationUserManager>();
        var signInManager = Context.GetOwinContext().Get<ApplicationSignInManager>();
        var user = new ApplicationUser() { UserName = Email.Text, Email = Email.Text };
        IdentityResult result = manager.Create(user, Password.Text);
        if (result.Succeeded)
        {
            // For more information on how to enable account confirmation and password reset please visit http://go.microsoft.com/fwlink/?LinkID=320771
            //string code = manager.GenerateEmailConfirmationToken(user.Id);
            //string callbackUrl = IdentityHelper.GetUserConfirmationRedirectUrl(code, user.Id, Request);
            //manager.SendEmail(user.Id, "Confirm your account", "Please confirm your account by clicking <a href=\"" + callbackUrl + "\">here</a>.");

            signInManager.SignIn(user, isPersistent: false, rememberBrowser: false);
            IdentityHelper.RedirectToReturnUrl(Request.QueryString["ReturnUrl"], Response);
        }
        else
        {
            ErrorMessage.Text = result.Errors.FirstOrDefault();
        }
    }
    else
    {
        ErrorMessage.Text = "We could not verify that you are a human.";
    }
}

这显示了在默认的Web应用程序的注册页面上实现的reCAPTCHA。if (reCaptchaResponse.Success)块中的代码块是页面的默认代码,其余部分是我的修改。请不要评论该代码块内的代码。

最后,前端:

代码语言:javascript
复制
<asp:Content runat="server" ID="HeadContent" ContentPlaceHolderID="HeadContent">
    <%-- This next line is where the <script> tag will end up. --%>
    <asp:Literal ID="reCaptchaHeadCode" runat="server"></asp:Literal>
</asp:Content>

<asp:Content runat="server" ID="BodyContent" ContentPlaceHolderID="MainContent">
    <h2><%: Title %>.</h2>
    <p class="text-danger">
        <asp:Literal runat="server" ID="ErrorMessage" />
    </p>

    <div class="form-horizontal">
        <h4>Create a new account</h4>
        <hr />
        <asp:ValidationSummary runat="server" CssClass="text-danger" />
        <div class="form-group">
            <asp:Label runat="server" AssociatedControlID="Email" CssClass="col-md-2 control-label">Email</asp:Label>
            <div class="col-md-10">
                <asp:TextBox runat="server" ID="Email" CssClass="form-control" TextMode="Email" />
                <asp:RequiredFieldValidator runat="server" ControlToValidate="Email"
                    CssClass="text-danger" ErrorMessage="The email field is required." />
            </div>
        </div>
        <div class="form-group">
            <asp:Label runat="server" AssociatedControlID="Password" CssClass="col-md-2 control-label">Password</asp:Label>
            <div class="col-md-10">
                <asp:TextBox runat="server" ID="Password" TextMode="Password" CssClass="form-control" />
                <asp:RequiredFieldValidator runat="server" ControlToValidate="Password"
                    CssClass="text-danger" ErrorMessage="The password field is required." />
            </div>
        </div>
        <div class="form-group">
            <asp:Label runat="server" AssociatedControlID="ConfirmPassword" CssClass="col-md-2 control-label">Confirm password</asp:Label>
            <div class="col-md-10">
                <asp:TextBox runat="server" ID="ConfirmPassword" TextMode="Password" CssClass="form-control" />
                <asp:RequiredFieldValidator runat="server" ControlToValidate="ConfirmPassword"
                    CssClass="text-danger" Display="Dynamic" ErrorMessage="The confirm password field is required." />
                <asp:CompareValidator runat="server" ControlToCompare="Password" ControlToValidate="ConfirmPassword"
                    CssClass="text-danger" Display="Dynamic" ErrorMessage="The password and confirmation password do not match." />
            </div>
        </div>
        <%-- This next line is where the body code will be filled. --%>
        <asp:Literal ID="reCaptchaBodyCode" runat="server"></asp:Literal>
        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <asp:Button runat="server" OnClick="CreateUser_Click" Text="Register" CssClass="btn btn-default" />
            </div>
        </div>
    </div>
</asp:Content>

请不要过多地评论标记,它提供了更多的上下文,并表明使用实际上是相当简单的。

如果有人使用这个,我不介意,请把这个帖子作为代码的原始源代码。

另外,下面是几张与它一起运行的图片:

EN

回答 2

Code Review用户

回答已采纳

发布于 2015-08-11 22:19:42

在构造函数中拥有可以抛出异常的操作被认为不是一个好形式--它应该简单地验证参数(并且异常抛出可以由您来完成),然后操作应该在一个方法中进行,比如Process或什么-不是。虽然在ReCaptchaResponse的例子中,它可能是一个简单的静态类:

代码语言:javascript
复制
/// <summary>
/// This class is used by the <see cref="ReCaptchaValidator"/> to return a proper response to a reCAPTCHA validation request.
/// </summary>
public static class ReCaptchaResponse
{
    /// <summary>
    /// Creates a new <see cref="ReCaptchaResponse"/> from the specified JSON string.
    /// </summary>
    /// <param name="jsonResponse">The JSON string to transform.</param>
    /// <param name="errors">Returns any <see cref="ReCaptchaErrors"/> that occurred during the reCAPTCHA response validation.</param>
    /// <returns>Returns a value indicating if the <see cref="ReCaptchaValidator"/> succeeded in validating the reCAPTCHA response or not.</returns>
    public static bool TryParseJson(string jsonResponse, out ReCaptchaErrors errors)
    {
        JavaScriptSerializer jss = new JavaScriptSerializer();
        dynamic deserializedJson = jss.DeserializeObject(jsonResponse);

        bool success = deserializedJson["success"];
        errors = ReCaptchaErrors.None;

        if (deserializedJson.ContainsKey("error-codes"))
        {
            foreach (string error in deserializedJson["error-codes"])
            {
                // Our `ReCaptchaErrors` enum contains the exact same strings as the returned `error` text would be, with the following transformations:
                // 1. The words are transformed to PascalCase;
                // 2. The dashes are stripped;
                string[] errorWords = error.Split('-');

                string errorEnumName = "";
                foreach (string errorWord in errorWords)
                    errorEnumName += errorWord[0].ToString().ToUpper() + errorWord.Substring(1);

                errors = errors | (ReCaptchaErrors)Enum.Parse(typeof(ReCaptchaErrors), errorEnumName);
            }
        }

        return success;
    }
}

可能通过将this添加到第一个参数使其成为一个扩展方法,并且可以这样调用它:

代码语言:javascript
复制
ReCaptchaErrors errors;

var success = reCaptchaResult.TryParseJson(out errors);
票数 4
EN

Code Review用户

发布于 2015-08-11 16:06:38

标志公共枚举ReCaptchaErrors {/ /没有发生错误。/ is = 0x00,/ /缺少秘密参数。/ MissingInputSecret = 0x01,/ /秘密参数无效或格式错误。/ InvalidInputSecret = 0x02,// /缺少响应参数。/ MissingInputResponse = 0x04,/ /响应参数无效或格式错误。/ InvalidInputResponse = 0x08,}

您正在分配这些值,因此enum选项的二进制值是0001001001001000等。这样您就可以将多个错误赋值给单个变量,对吗?

如果您使用了位移位,您将更清楚地知道您到底在做什么,如下所示:

代码语言:javascript
复制
[Flags]
public enum ReCaptchaErrors
{
    /// <summary>
    /// No errors occurred.
    /// </summary>
    None = 0,
    /// <summary>
    /// The secret parameter is missing.
    /// </summary>
    MissingInputSecret = 1 << 0,
    /// <summary>
    /// The secret parameter is invalid or malformed.
    /// </summary>
    InvalidInputSecret = 1 << 1,
    /// <summary>
    /// The response parameter is missing.
    /// </summary>
    MissingInputResponse = 1 << 2,
    /// <summary>
    /// The response parameter is invalid or malformed.
    /// </summary>
    InvalidInputResponse = 1 << 3,
}

现在,您不能意外地通过键入0x09而不是0x08来破坏代码,而且很明显,您希望每个标志都是它自己的位。有关更多信息,您可以通过我们自己的@nhgrif阅读这个博客帖子

票数 4
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/100604

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档