在ASP.NET的服务器端验证Recaptcha 2(无CAPTCHA reCAPTCHA)

本文关键字:CAPTCHA reCAPTCHA Recaptcha NET ASP 服务器端 验证 | 更新日期: 2023-09-27 18:36:02

新的Recaptcha 2看起来很有前途,但我没有找到在ASP中验证它的方法。NET的服务器端,

if(Page.IsValid) 在此答案中,对旧的验证码有效,但对新的验证码无效,

如何在服务器端验证新的 reCAPTCHA?

在ASP.NET的服务器端验证Recaptcha 2(无CAPTCHA reCAPTCHA)

在阅读了许多资源之后,我最终编写了这个类来处理新 ReCaptcha 的验证:

正如这里提到的:当最终用户解决reCAPTCHA时,一个新的字段(g-recaptcha-response)将被填充到HTML中。

我们需要读取此值并将其传递给下面的类来验证它:

在 C# 中:

在页面后面的代码中:

string EncodedResponse = Request.Form["g-Recaptcha-Response"];
bool IsCaptchaValid = (ReCaptchaClass.Validate(EncodedResponse) == "true" ? true : false);
if (IsCaptchaValid) {
    //Valid Request
}

班级:

  using Newtonsoft.Json;
    public class ReCaptchaClass
    {
        public static string Validate(string EncodedResponse)
        {
            var client = new System.Net.WebClient();
            string PrivateKey = "6LcH-v8SerfgAPlLLffghrITSL9xM7XLrz8aeory";
            var GoogleReply = client.DownloadString(string.Format("https://www.google.com/recaptcha/api/siteverify?secret={0}&response={1}", PrivateKey, EncodedResponse));
            var captchaResponse = Newtonsoft.Json.JsonConvert.DeserializeObject<ReCaptchaClass>(GoogleReply);
            return captchaResponse.Success.ToLower();
        }
        [JsonProperty("success")]
        public string Success
        {
            get { return m_Success; }
            set { m_Success = value; }
        }
        private string m_Success;
        [JsonProperty("error-codes")]
        public List<string> ErrorCodes
        {
            get { return m_ErrorCodes; }
            set { m_ErrorCodes = value; }
        }

        private List<string> m_ErrorCodes;
    }

在 VB.NET:

在页面后面的代码中:

Dim EncodedResponse As String = Request.Form("g-Recaptcha-Response")
    Dim IsCaptchaValid As Boolean = IIf(ReCaptchaClass.Validate(EncodedResponse) = "True", True, False)
    If IsCaptchaValid Then
        'Valid Request
    End If

班级:

Imports Newtonsoft.Json

Public Class ReCaptchaClass
    Public Shared Function Validate(ByVal EncodedResponse As String) As String
        Dim client = New System.Net.WebClient()
        Dim PrivateKey As String = "6dsfH-v8SerfgAPlLLffghrITSL9xM7XLrz8aeory"
        Dim GoogleReply = client.DownloadString(String.Format("https://www.google.com/recaptcha/api/siteverify?secret={0}&response={1}", PrivateKey, EncodedResponse))
        Dim captchaResponse = Newtonsoft.Json.JsonConvert.DeserializeObject(Of ReCaptchaClass)(GoogleReply)
        Return captchaResponse.Success
    End Function
    <JsonProperty("success")> _
    Public Property Success() As String
        Get
            Return m_Success
        End Get
        Set(value As String)
            m_Success = value
        End Set
    End Property
    Private m_Success As String
    <JsonProperty("error-codes")> _
    Public Property ErrorCodes() As List(Of String)
        Get
            Return m_ErrorCodes
        End Get
        Set(value As List(Of String))
            m_ErrorCodes = value
        End Set
    End Property
    Private m_ErrorCodes As List(Of String)
End Class

这是一个使用 JavaScriptSerializer 的版本。感谢 Ala 提供此代码的基础。

网页配置应用设置 -在我的例子中,我已经将密钥添加到 Web.Config 中,以允许环境之间的转换。如果需要,也可以在此处轻松加密。

<add key="Google.ReCaptcha.Secret" value="123456789012345678901234567890" />

ReCaptcha 类 - 一个简单的类,用于将响应参数与您的密钥一起发布到 Google 并进行验证。响应使用 .Net JavaScriptSerializer 类反序列化,并从该 true 或 false 返回。

using System.Collections.Generic;
using System.Configuration;
public class ReCaptcha
{   
    public bool Success { get; set; }
    public List<string> ErrorCodes { get; set; }
    public static bool Validate(string encodedResponse)
    {
        if (string.IsNullOrEmpty(encodedResponse)) return false;
        var client = new System.Net.WebClient();
        var secret = ConfigurationManager.AppSettings["Google.ReCaptcha.Secret"];
        if (string.IsNullOrEmpty(secret)) return false;
        var googleReply = client.DownloadString(string.Format("https://www.google.com/recaptcha/api/siteverify?secret={0}&response={1}", secret, encodedResponse));
        var serializer = new System.Web.Script.Serialization.JavaScriptSerializer();
        var reCaptcha = serializer.Deserialize<ReCaptcha>(googleReply);
        return reCaptcha.Success;
    }
}

验证响应 - 检查控制器中 g-Recaptcha-Response 表单参数(或 Web 表单的代码隐藏)的有效性,并采取适当的操作。

var encodedResponse = Request.Form["g-Recaptcha-Response"];
var isCaptchaValid = ReCaptcha.Validate(encodedResponse);
if (!isCaptchaValid)
{
    // E.g. Return to view or set an error message to visible
}   

这些答案中的大多数似乎比需要的要复杂得多。他们也没有指定有助于防止拦截攻击的IP(https://security.stackexchange.com/questions/81865/is-there-any-reason-to-include-the-remote-ip-when-using-recaptcha)。这是我决定的

public bool CheckCaptcha(string captchaResponse, string ipAddress)
{
    using (var client = new WebClient())
    {
        var response = client.DownloadString($"https://www.google.com/recaptcha/api/siteverify?secret={ ConfigurationManager.AppSettings["Google.ReCaptcha.Secret"] }&response={ captchaResponse }&remoteIp={ ipAddress }");
        return (bool)JObject.Parse(response)["success"];
    }
}
您可以使用

"IsValidCaptcha()"方法在服务器端验证您的Google recaptcha。在以下方法中将您的密钥替换为"您的验证码密钥"。

Public bool IsValidCaptcha()
 {
  string resp = Request["g-recaptcha-response"];
  var req = (HttpWebRequest)WebRequest.Create
            (https://www.google.com/recaptcha/api/siteverify?secret=+ YourRecaptchaSecretkey + "&response=" + resp);
     using (WebResponse wResponse = req.GetResponse()) 
       {
       using (StreamReader readStream = new StreamReader(wResponse.GetResponseStream()))
         {
          string jsonResponse = readStream.ReadToEnd();
          JavaScriptSerializer js = new JavaScriptSerializer();
          // Deserialize Json
          CaptchaResult data = js.Deserialize<CaptchaResult>(jsonResponse); 
            if (Convert.ToBoolean(data.success))
              {
               return true;
              }
         }
      }
     return false;
 }

还要创建以下类。

public class CaptchaResult
  {
   public string success { get; set; }
  }

根据文档,您只需将密钥和用户答案发布到 API 并读取返回的"成功"属性

简短回答:

        var webClient = new WebClient();
        string verification = webClient.DownloadString(string.Format("https://www.google.com/recaptcha/api/siteverify?secret={0}&response={1}", secretKey, userResponse));
        if (JObject.Parse(verification)["success"].Value<bool>())
        {
            // SUCCESS!!!

完整示例:

假设您在 IamNotARobotLogin.cshtml 中实现此页面。

<head>
 <script src="https://www.google.com/recaptcha/api.js" async defer></script>
</head>
<body>
<form action="Login" method="POST">
  <div class="g-recaptcha" data-sitekey="your_site_key"></div><br/>
  <input type="submit" value="Log In">
</form>
</body>

假设您希望控制器在验证成功时在会话中保存"I_AM_NOT_ROBOT"标志:

    public ActionResult IamNotARobotLogin()
    {
        return View();
    }
    [HttpPost]
    public ActionResult Login()
    {
        const string secretKey = "6LcH-v8SerfgAPlLLffghrITSL9xM7XLrz8aeory";
        string userResponse = Request.Form["g-Recaptcha-Response"];
        var webClient = new System.Net.WebClient();
        string verification = webClient.DownloadString(string.Format("https://www.google.com/recaptcha/api/siteverify?secret={0}&response={1}", secretKey, userResponse));
        var verificationJson = Newtonsoft.Json.Linq.JObject.Parse(verification);
        if (verificationJson["success"].Value<bool>())
        {
            Session["I_AM_NOT_A_ROBOT"] = "true";
            return RedirectToAction("Index", "Demo");
        }
        // try again:
        return RedirectToAction("IamNotARobotLogin");
    }
这是我对

Ala 解决方案的分支,以便:

  • 在开机自检中发送参数
  • 清理表单输入
  • 包括请求者 IP 地址
  • 将密钥存储在 Web.Config 中:

在控制器中:

bool isCaptchaValid = await ReCaptchaClass.Validate(this.Request);
if (!isCaptchaValid)
{       
    ModelState.AddModelError("", "Invalid captcha");
    return View(model);
}

实用程序类:

public class ReCaptchaClass
{
    private static ILog log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
    private static string SecretKey = System.Configuration.ConfigurationManager.AppSettings["Google.ReCaptcha.Secret"];
    [JsonProperty("success")]
    public bool Success { get; set; }
    [JsonProperty("error-codes")]
    public List<string> ErrorCodes { get; set; }
    public static async Task<bool> Validate(HttpRequestBase Request)
    {
        string encodedResponse = Request.Form["g-Recaptcha-Response"];          
        string remoteIp = Request.UserHostAddress;          
        using (var client = new HttpClient())
        {
            var values = new Dictionary<string, string>
            {
               {"secret", SecretKey},
               {"remoteIp", remoteIp},
               {"response", encodedResponse}
            };
            var content = new FormUrlEncodedContent(values);
            var response = await client.PostAsync("https://www.google.com/recaptcha/api/siteverify", content);
            var responseString = await response.Content.ReadAsStringAsync();
            var captchaResponse = Newtonsoft.Json.JsonConvert.DeserializeObject<ReCaptchaClass>(responseString);
            if ((captchaResponse.ErrorCodes?.Count ?? 0) != 0)
            {
                log.Warn("ReCaptcha errors: " + string.Join("'n", captchaResponse.ErrorCodes));
            }
            return captchaResponse.Success;
        }
    }       
}

本文清楚地逐步说明了如何在模型上实现 ReCaptcha 验证属性。

首先,创建验证码验证属性。

namespace Sample.Validation
{
    public class GoogleReCaptchaValidationAttribute : ValidationAttribute
    {
        protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            Lazy<ValidationResult> errorResult = new Lazy<ValidationResult>(() => new ValidationResult("Google reCAPTCHA validation failed", new String[] { validationContext.MemberName }));
            if (value == null || String.IsNullOrWhiteSpace( value.ToString())) 
            {
                return errorResult.Value;
            }
            IConfiguration configuration = (IConfiguration)validationContext.GetService(typeof(IConfiguration));
            String reCaptchResponse = value.ToString();
            String reCaptchaSecret = configuration.GetValue<String>("GoogleReCaptcha:SecretKey");
            HttpClient httpClient = new HttpClient();
            var httpResponse = httpClient.GetAsync($"https://www.google.com/recaptcha/api/siteverify?secret={reCaptchaSecret}&response={reCaptchResponse}").Result;
            if (httpResponse.StatusCode != HttpStatusCode.OK)
            {
                return errorResult.Value;
            }
            String jsonResponse = httpResponse.Content.ReadAsStringAsync().Result;
            dynamic jsonData = JObject.Parse(jsonResponse);
            if (jsonData.success != true.ToString().ToLower())
            {
                return errorResult.Value;
            }
            return ValidationResult.Success;
        }
    }
}

然后在模型上添加验证属性。

namespace Sample.Models
{
    public class XModel
    {
        // ...
        [Required]  
        [GoogleReCaptchaValidation]  
        public String GoogleReCaptchaResponse { get; set; }
    }
}

最后,您只需调用 ModelState.IsValid 方法即可。

namespace Sample.Api.Controllers
{
    [ApiController]
    public class XController : ControllerBase
    {
        [HttpPost]
        public IActionResult Post(XModel model)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }
            // ...
        }
    }
}

瞧,:)

另一个例子发布在这里:

RecaptchaV2.NET (Github)

它还实现了 Recaptcha 2.0 的安全令牌选项(查看该位的完整源代码,我删除了相关的代码片段仅用于验证结果)。

这个不依赖于Newtonsoft的json解析器,而是使用内置的.NET解析器。

以下是来自 RecaptchaV2.NET 库的相关代码片段(来自recaptcha.cs):

namespace RecaptchaV2.NET
{
  /// <summary>
  /// Helper Methods for the Google Recaptcha V2 Library
  /// </summary>
  public class Recaptcha
  {
    public string SiteKey { get; set; }
    public string SecretKey { get; set; }
    public Guid SessionId { get; set; }
    /// <summary>
    /// Validates a Recaptcha V2 response.
    /// </summary>
    /// <param name="recaptchaResponse">g-recaptcha-response form response variable (HttpContext.Current.Request.Form["g-recaptcha-response"])</param>
    /// <returns>RecaptchaValidationResult</returns>
    public RecaptchaValidationResult Validate(string recaptchaResponse)
    {
      RecaptchaValidationResult result = new RecaptchaValidationResult();
      HttpWebRequest req = (HttpWebRequest)WebRequest.Create("https://www.google.com/recaptcha/api/siteverify?secret=" + SecretKey + "&response="
        + recaptchaResponse + "&remoteip=" + GetClientIp());
      //Google recaptcha Response
      using (WebResponse wResponse = req.GetResponse())
      {
        using (StreamReader readStream = new StreamReader(wResponse.GetResponseStream()))
        {
          string jsonResponse = readStream.ReadToEnd();
          JavaScriptSerializer js = new JavaScriptSerializer();
          result = js.Deserialize<RecaptchaValidationResult>(jsonResponse.Replace("error-codes", "ErrorMessages").Replace("success", "Succeeded"));// Deserialize Json
        }
      }
      return result;
    }
    private string GetClientIp()
    {
      // Look for a proxy address first
      String _ip = HttpContext.Current.Request.ServerVariables["HTTP_X_FORWARDED_FOR"];
      // If there is no proxy, get the standard remote address
      if (string.IsNullOrWhiteSpace(_ip) || _ip.ToLower() == "unknown")
        _ip = HttpContext.Current.Request.ServerVariables["REMOTE_ADDR"];
      return _ip;
    }
  }
  public class RecaptchaValidationResult
  {
    public RecaptchaValidationResult()
    {
      ErrorMessages = new List<string>();
      Succeeded = false;
    }
    public List<string> ErrorMessages { get; set; }
    public bool Succeeded { get; set; }
    public string GetErrorMessagesString()
    {
      return string.Join("<br/>", ErrorMessages.ToArray());
    }
  }
}

Google的ReCaptcha API不再接受有效负载作为GET请求中的查询字符串参数。 谷歌总是返回一个"错误"的成功响应,除非我通过HTTP POST发送数据。 以下是对 Ala 的(优秀!)类的更新,该类将有效负载发布到 Google 服务端点:

using Newtonsoft.Json;
using System.Net;
using System.IO;
using System.Text;
public class RecaptchaHandler
{
    public static string Validate(string EncodedResponse, string RemoteIP)
    {
        var client = new WebClient();
        string PrivateKey = "PRIVATE KEY";
        WebRequest req = WebRequest.Create("https://www.google.com/recaptcha/api/siteverify");
        string postData = String.Format("secret={0}&response={1}&remoteip={2}",
                                         PrivateKey,
                                         EncodedResponse,
                                         RemoteIP);
        byte[] send = Encoding.Default.GetBytes(postData);
        req.Method = "POST";
        req.ContentType = "application/x-www-form-urlencoded";
        req.ContentLength = send.Length;
        Stream sout = req.GetRequestStream();
        sout.Write(send, 0, send.Length);
        sout.Flush();
        sout.Close();
        WebResponse res = req.GetResponse();
        StreamReader sr = new StreamReader(res.GetResponseStream());
        string returnvalue = sr.ReadToEnd();
        var captchaResponse = JsonConvert.DeserializeObject<RecaptchaHandler>(returnvalue);
        return captchaResponse.Success;
    }
    [JsonProperty("success")]
    public string Success
    {
        get { return m_Success; }
        set { m_Success = value; }
    }
    private string m_Success;
    [JsonProperty("error-codes")]
    public List<string> ErrorCodes
    {
        get { return m_ErrorCodes; }
        set { m_ErrorCodes = value; }
    }
    private List<string> m_ErrorCodes;
}

使用动态在服务器端验证验证码

调用函数

[HttpPost]
public ActionResult ClientOrderDetail(FormCollection collection, string EncodedResponse)
{
    Boolean Validation = myFunction.ValidateRecaptcha(EncodedResponse);
    return View();
}

函数声明

public static Boolean ValidateRecaptcha(string EncodedResponse)
{
    string PrivateKey = "YourSiteKey";
    var client = new System.Net.WebClient();
    var GoogleReply = client.DownloadString(string.Format("https://www.google.com/recaptcha/api/siteverify?secret={0}&response={1}", PrivateKey, EncodedResponse));
    var serializer = new JavaScriptSerializer();
    dynamic data = serializer.Deserialize(GoogleReply, typeof(object));
    Boolean Status = data["success"];
    string challenge_ts = data["challenge_ts"];
    string hostname = data["hostname"];
    return Status;
}

我在此帖子中发布的示例使用Newtonsoft.JSON反序列化完整返回的JSON,将数据发布到Google(而不是使用查询字符串)将相关变量存储在web.config中而不是硬编码。