仅当用户是人类(reCaptcha v3)时,如何加载和插入一些 HTML?
Posted
技术标签:
【中文标题】仅当用户是人类(reCaptcha v3)时,如何加载和插入一些 HTML?【英文标题】:How to load and insert some HTML only if the user is human (reCaptcha v3)? 【发布时间】:2021-12-10 23:45:51 【问题描述】:目标
为了保护网络应用免受恶意垃圾邮件机器人爬虫和类似恶意行为者的攻击,我的目标是使用 reCAPTCHA v3 来分析访问该网站的用户,如果 Captcha v3 分数足够好(比如 0.5 或更高),请使用 Fetch用于 POST 令牌并对其进行验证的 API,如果分数足够好,如前所述,则在某些 html 中返回电子邮件地址。为简单起见,function loadContactbubble()
在您单击按钮时被执行。
问题
我不确定在哪里实施if (response.score => 0.5)
检查。
前端半工作在这方面,在网络浏览器调试工具中它会给出响应,但在控制台中它将响应打印为undefined
我的实施是否足够安全?密钥不能以某种方式被抽走或类似的吗?
我在浏览器中收到很多 CSP 警告,这可能是生产中的问题吗?
代码
我一直在关注这个指南:https://dev.to/spencer741/google-recaptcha-v3-server-side-validation-using-asp-net-core-5-0-3hfb(这意味着代码与本文的 80-90% 相似)
我的 appsettings.json 包含密钥和 siteverify 链接(API 链接)。
GHttpModels.cs:
using System;
using System.Runtime.Serialization;
namespace _projectname.Tooling
public class GRequestModel
public string path get; set;
public string secret get; set;
public string response get; set;
public string remoteip get; set;
public GRequestModel(string res, string remip)
response = res;
remoteip = remip;
secret = Startup.Configuration["GoogleRecaptchaV3:Secret"];
path = Startup.Configuration["GoogleRecaptchaV3:ApiUrl"];
if (String.IsNullOrWhiteSpace(secret) || String.IsNullOrWhiteSpace(path))
//Invoke logger
throw new Exception("Invalid 'Secret' or 'Path' properties in appsettings.json. Parent: GoogleRecaptchaV3.");
//Google's response property naming is
//embarrassingly inconsistent, that's why we have to
//use DataContract and DataMember attributes,
//so we can bind the class from properties that have
//naming where a C# variable by that name would be
//against the language specifications... (i.e., '-').
[DataContract]
public class GResponseModel
[DataMember]
public bool success get; set;
[DataMember]
public string challenge_ts get; set;
[DataMember]
public string hostname get; set;
//Could create a child object for
//error-codes
[DataMember(Name = "error-codes")]
public string[] error_codes get; set;
GoogleReCaptchaV3Service.cs:
using System;
using System.Threading.Tasks;
using System.Text.Json;
using System.Web;
using System.Net.Http;
using System.IO;
using System.Text;
using System.Runtime.Serialization.Json;
namespace _projectname.Tooling
public class CaptchaRequestException : Exception
public CaptchaRequestException()
public CaptchaRequestException(string message)
: base(message)
public CaptchaRequestException(string message, Exception inner)
: base(message, inner)
public interface IGoogleRecaptchaV3Service
HttpClient _httpClient get; set;
GRequestModel Request get; set;
GResponseModel Response get; set;
void InitializeRequest(GRequestModel request);
Task<bool> Execute();
public class GoogleRecaptchaV3Service : IGoogleRecaptchaV3Service
public HttpClient _httpClient get; set;
public GRequestModel Request get; set;
public GResponseModel Response get; set;
public HttpRequestException HttpReqException get; set;
public Exception GeneralException get; set;
public GoogleRecaptchaV3Service(HttpClient httpClient)
_httpClient = httpClient;
public void InitializeRequest(GRequestModel request)
Request = request;
public async Task<bool> Execute()
// Notes on error handling:
// Google will pass back a 200 Status Ok response if no network or server errors occur.
// If there are errors in on the "business" level, they will be coded in an array;
// CaptchaRequestException is for these types of errors.
// CaptchaRequestException and multiple catches are used to help seperate the concerns of
// a) an HttpRequest 400+ status code
// b) an error at the "business" level
// c) an unpredicted error that can only be handled generically.
// It might be worthwhile to implement a "user error message" property in this class so the
// calling procedure can decide what, if anything besides a server error, to return to the
// client and any client handling from there on.
try
//Don't to forget to invoke any loggers in the logic below.
//formulate request
string builtURL = Request.path + '?' + HttpUtility.UrlPathEncode($"secret=Request.secret&response=Request.response&remoteip=Request.remoteip");
StringContent content = new StringContent(builtURL);
Console.WriteLine($"Sent Request builtURL");
//send request, await.
HttpResponseMessage response = await _httpClient.PostAsync(builtURL, null);
response.EnsureSuccessStatusCode();
//read response
byte[] res = await response.Content.ReadAsByteArrayAsync();
string logres = await response.Content.ReadAsStringAsync();
Console.WriteLine($"Retrieved Response: logres");
//Serialize into GReponse type
using (MemoryStream ms = new MemoryStream(res))
DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(GResponseModel));
Response = (GResponseModel)serializer.ReadObject(ms);
//check if business success
if (!Response.success)
throw new CaptchaRequestException();
//return bool.
return true; //response.IsSuccessStatusCode; <- don't need this. EnsureSuccessStatusCode is now in play.
catch (HttpRequestException hre)
//handle http error code.
HttpReqException = hre;
//invoke logger accordingly
//only returning bool. It is ultimately up to the calling procedure
//to decide what data it wants from the Service.
return false;
catch (CaptchaRequestException ex)
//Business-level error... values are accessible in error-codes array.
//this catch block mainly serves for logging purposes.
/* Here are the possible "business" level codes:
missing-input-secret The secret parameter is missing.
invalid-input-secret The secret parameter is invalid or malformed.
missing-input-response The response parameter is missing.
invalid-input-response The response parameter is invalid or malformed.
bad-request The request is invalid or malformed.
timeout-or-duplicate The response is no longer valid: either is too old or has been used previously.
*/
//invoke logger accordingly
//only returning bool. It is ultimately up to the calling procedure
//to decide what data it wants from the Service.
return false;
catch (Exception ex)
// Generic unpredictable error
GeneralException = ex;
// invoke logger accordingly
//only returning bool. It is ultimately up to the calling procedure
//to decide what data it wants from the Service.
return false;
Startup.cs:
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Http;
//from captchav3
using _projectname.Tooling;
namespace _projectname
public class CookieCheckMiddleware
private readonly RequestDelegate _next;
public CookieCheckMiddleware(RequestDelegate next)
_next = next;
public async Task Invoke(HttpContext httpContext)
if (httpContext.Request.Cookies["ModalShown"] == null && httpContext.Request.Path != "/Cookies")
httpContext.Response.Redirect("/Cookies?q="+ httpContext.Request.Path);
await _next(httpContext); // calling next middleware
// Extension method used to add the middleware to the HTTP request pipeline.
public static class CookieCheckMiddlewareExtensions
public static IApplicationBuilder UseCookieCheckMiddleware(this IApplicationBuilder builder)
return builder.UseMiddleware<CookieCheckMiddleware>();
public class Startup
internal static IConfiguration Configuration get; private set;
public Startup(IConfiguration configuration)
Configuration = configuration;
//public IConfiguration Configuration get;
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
// Captcha v3
services.AddHttpClient<IGoogleRecaptchaV3Service, GoogleRecaptchaV3Service>();
services.AddTransient<IGoogleRecaptchaV3Service, GoogleRecaptchaV3Service>();
services.AddControllers();
//Register dependencies
services.AddRazorPages();
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie();
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
if (env.IsDevelopment())
app.UseDeveloperExceptionPage();
else
//app.Use(async (ctx, next) =>
//
// await next();
// if (ctx.Response.StatusCode == 404 && !ctx.Response.HasStarted)
//
// //Re-execute the request so the user gets the error page
// string originalPath = ctx.Request.Path.Value;
// ctx.Items["originalPath"] = originalPath;
// ctx.Request.Path = "/Cloud";
// await next();
//
//);
// orig
//app.UseExceptionHandler("/Errors/0");
app.UseStatusCodePagesWithRedirects("/Errors/0");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
// TEST
app.UseCookieCheckMiddleware();
app.UseAuthentication();
app.UseAuthorization();
var cookiePolicyOptions = new CookiePolicyOptions
MinimumSameSitePolicy = SameSiteMode.Strict,
;
app.UseCookiePolicy(cookiePolicyOptions);
app.UseEndpoints(endpoints =>
endpoints.MapRazorPages();
// Experimental
endpoints.MapControllers();
);
_Layout.cshtml:
<button onclick="loadContactbubble();">Load contacts</button>
site.js(为简洁起见仅提供函数):
function loadContactbubble()
grecaptcha.execute('sitekeyhere', action: 'onclick' ).then(function (token)
console.log(token);
fetch("/load/contactbubble?RecaptchaToken=" + token,
method: "POST",
body: token,
)
).then((response) =>
console.log(response);
if (!response.ok)
const errorBuild =
type: "Error",
message: response.message || "Something went wrong",
data: response.data || "",
code: response.code || "",
;
//addText("Error: " + JSON.stringify(errorBuild));
//toggleLoader(2, "hidden");
//return;
)
ApiController.cs:
using Microsoft.AspNetCore.Mvc;
using System.Net.Http;
using _projectname.Tooling;
using System.Threading.Tasks;
namespace _projectname.Controllers
public class SignUpModel
public string RecaptchaToken get; set;
[ApiController]
[Route("load/contactbubble")]
public class SignUp : ControllerBase
IGoogleRecaptchaV3Service _gService get; set;
public SignUp(IGoogleRecaptchaV3Service gService)
_gService = gService;
[HttpPost]
public async Task<IActionResult> Post([FromQuery] SignUpModel SignUpData)
GRequestModel rm = new GRequestModel(SignUpData.RecaptchaToken,
HttpContext.Connection.RemoteIpAddress.ToString());
_gService.InitializeRequest(rm);
if (!await _gService.Execute())
//return error codes string.
return Ok(_gService.Response.error_codes);
//call Business layer
//return result
return base.Content("<div>Welcome human! Here is our secret e-mail: test@test.com</div>", "text/html");
错误
如果我点击按钮,控制台会打印出以下内容:
Uncaught (in promise) TypeError: can't access property "ok", response is undefined
响应在网络选项卡中包含正确的 HTML,这很奇怪。
我该如何解决这个问题?
【问题讨论】:
【参考方案1】:你的功能
function (token)
console.log(token);
fetch("/load/contactbubble?RecaptchaToken=" + token,
method: "POST",
body: token,
);
不返回任何内容,因此传递给下一个.then((response) => ...
的参数是未定义。
让这个函数返回获取的数据,它应该可以工作:
function (token)
console.log(token);
return fetch("/load/contactbubble?RecaptchaToken=" + token,
method: "POST",
body: token
);
(好吧,它至少应该将获取结果转发到下一个.then((response) => ...
。我没有在你的代码中寻找其他错误,所以“它应该可以工作”是了解我在这里解释的一个问题...)
【讨论】:
欢迎来到社区 :-) 似乎添加 return 以某种奇怪的格式返回响应,这意味着.then((response) =>
... 由于某种原因不起作用:-(
没关系,response.ok 工作正常,现在如何附加 HTML 内容?我只看到四种响应类型,并且文本不是我想要的,以便将 HTML 附加到页面中..以上是关于仅当用户是人类(reCaptcha v3)时,如何加载和插入一些 HTML?的主要内容,如果未能解决你的问题,请参考以下文章