这是我(不到一小时前)关于我的Google reCAPTCHA C#实现的另一篇文章的后续文章:Google reCAPTCHA Validator。
这增加了对错误消息的支持,以便您可以确定reCAPTCHA请求失败的原因(如果返回了错误)。
如果在将ReCaptchaLocationInclude设置为非空列表之前使用ExtraClasses,也会修复错误。
我为错误添加了一个枚举:
/// <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响应的类:
/// <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验证器:
/// <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(),然后添加新类即可。)
用法:
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)块中的代码块是页面的默认代码,其余部分是我的修改。请不要评论该代码块内的代码。
最后,前端:
<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>请不要过多地评论标记,它提供了更多的上下文,并表明使用实际上是相当简单的。
如果有人使用这个,我不介意,请把这个帖子作为代码的原始源代码。
另外,下面是几张与它一起运行的图片:


发布于 2015-08-11 22:19:42
在构造函数中拥有可以抛出异常的操作被认为不是一个好形式--它应该简单地验证参数(并且异常抛出可以由您来完成),然后操作应该在一个方法中进行,比如Process或什么-不是。虽然在ReCaptchaResponse的例子中,它可能是一个简单的静态类:
/// <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添加到第一个参数使其成为一个扩展方法,并且可以这样调用它:
ReCaptchaErrors errors;
var success = reCaptchaResult.TryParseJson(out errors);发布于 2015-08-11 16:06:38
标志公共枚举ReCaptchaErrors {/ /没有发生错误。/ is = 0x00,/ /缺少秘密参数。/ MissingInputSecret = 0x01,/ /秘密参数无效或格式错误。/ InvalidInputSecret = 0x02,// /缺少响应参数。/ MissingInputResponse = 0x04,/ /响应参数无效或格式错误。/ InvalidInputResponse = 0x08,}
您正在分配这些值,因此enum选项的二进制值是0001、0010、0100、1000等。这样您就可以将多个错误赋值给单个变量,对吗?
如果您使用了位移位,您将更清楚地知道您到底在做什么,如下所示:
[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阅读这个博客帖子。
https://codereview.stackexchange.com/questions/100604
复制相似问题