0%

ASP.NET、NetCoreAPI整合reCAPTCHA V2記錄

ASP.NET、NetCoreAPI整合reCAPTCHA V2記錄

1. Google 服務申請與設定

1.1. 註冊新網站

輸入以下網址,會看到下圖的結果:

1
https://www.google.com/recaptcha/admin

image-20240117093252504

  • 標籤

    • 任一填寫
  • 類型

    • 這邊使用v2,「我不是機器人」核取方塊
  • 網域

    • 測試階段加入localhost

1.2. 產生金鑰

點選提交按鈕後,會跳轉到下圖的頁面,其中有兩個金鑰後續會使用

image-20240117093016927

1.3. 重新進入admin介面

1
https://www.google.com/recaptcha/admin

可以看到已成功建立,並於首頁出現相關面版資訊,如下圖:

image-20240117093845844

2. WebForm 程式開發環境說明

  • Visual Studio 2022
  • ASP.NET WebForm

2.1. VS2022 WebForm環境建置 (Visual Studio Installer)

  1. 找出此軟體進行安裝,如下:

image-20240117100529577

  1. 開啟軟體後,勾選紅框選項進行安裝,如下:

image-20240117100457948

  1. 建立新專案

    第二步驟安裝完成後,建立新專案就會看到新的選項囉,如下:

image-20240117100638981

2.2. 時序圖

簡單來說,大致粗分為:

  • 渲染出reCAPTCHA圖形驗證於client 端 browser
  • WebServer與Google進行溝通驗證
  • 回傳驗證結果至client端

Google Recaptcha V2

圖片參考來源來自dejanstojanovic.net

2.3. 程式實作範例

2.3.1. WebForm.aspx

這隻程式的重點在兩個部分:

1
<script src="https://www.google.com/recaptcha/api.js" async defer></script>
1
<div class="g-recaptcha" data-sitekey="6L...EA"></div>

WebForm.aspx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="WebForm.aspx.cs" Inherits="ASP_NET_reCAPTCHA.WebForm" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title></title>
<script src="https://www.google.com/recaptcha/api.js" async defer></script>
</head>
<body>
<form id="form1" runat="server">
<div>
帳號:<asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>
密碼:<asp:TextBox ID="TextBox2" runat="server"></asp:TextBox>
<div class="g-recaptcha" data-sitekey="6L...EA"></div>
<asp:Button ID="btnSubmit" runat="server" Text="送出" OnClick="btnSubmit_Click" />
<asp:Label ID="lblMsg" runat="server" Text=""></asp:Label>
</div>

</form>
</body>
</html>

2.3.2. WebForm.aspx.cs

當點擊送出按鈕後,會跑到btnSubmit_Click事件,接著讓server與google進行驗證確認,重點有幾項:

apiKey

1
var apiKey = "6L...Dh";

g-recaptcha-response

1
var gRecaptchaResponse = Request.Form["g-recaptcha-response"];

WebForm.aspx.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Services.Description;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Xml.Linq;

namespace ASP_NET_reCAPTCHA
{
public partial class WebForm : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{

}

protected void btnSubmit_Click(object sender, EventArgs e)
{
var apiKey = "6L...Dh";
var url = "https://www.google.com/recaptcha/api/siteverify";
var wc = new System.Net.WebClient();
wc.Headers.Add("Content-Type", "application/x-www-form-urlencoded");
var gRecaptchaResponse = Request.Form["g-recaptcha-response"];
var data = "secret=" + apiKey + "&response=" + gRecaptchaResponse;
var json = wc.UploadString(url, data);
// JSON 反序化取 .success 屬性 true/false 判斷
var success = JsonConvert.DeserializeObject<JObject>(json).Value<bool>("success");
if (!success)
{
lblMsg.Text = "驗證碼有誤";
return;
}
// TODO: 檢查帳號密碼
lblMsg.Text = "確認過眼神,你不是機器人,但程式還沒完成";
}
}
}
2.3.3. 測試與觀查
2.3.3.1. 第一次載入畫面

image-20240117110956325

2.3.3.2. 觀查點選我不是機器人核取方塊

點選前與後,在document.getElementById("g-recaptcha-response").value值的變化,如下圖紅框:

image-20240117112239984

2.3.3.3. 最後結果

image-20240117111104999

3. .NET CORE API 程式開發環境說明

  • NET CORE 6.5.0

3.1. VS2022 NET CORE API 環境建置

key api找到對應專案類型,如下:

image-20240120215451256

3.2. 程式實作範例

3.2.1. 前端程式碼

準備三個檔案index.htmlscript.jsstyle.css,最後結果如下圖:

image-20240120220347776

3.2.2. wwwroot\index.html

跟webform一樣,需要g-recaptcha與引入https://www.google.com/recaptcha/api.js。差別是送出的click透過ajax實現驗證。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!DOCTYPE html>
<head>
<title>Login Page</title>
<link rel="stylesheet" href="style.css">
<script type="text/javascript" src="https://www.google.com/recaptcha/api.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<script src="script.js"></script>
</head>
<body>
<div class="login-page">
<div class="form">
<div class="login-form">
<input type="text" placeholder="Username" />
<input type="password" placeholder="Password" />
<div class="g-recaptcha" data-sitekey="6Lc...EA"></div><br>
<button onclick="LoginButton()">Login</button>
</div>
</div>
</div>
</body>
</html>
3.2.3. wwwroot\script.js

透過ajax方式call User/Captcha

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function LoginButton() {
const reCaptchaResponse = grecaptcha.getResponse();
if (reCaptchaResponse) {
$.ajax({
type: "GET",
url: "https://localhost:7166/api/User/Captcha",
data: { userResponse: reCaptchaResponse },
success: function (data) {
if (data) {
//API returned true
alert("Captcha Verified");
} else {
//API returned false
alert("Please verify captcha again");
}
},
error: function (error) {
alert("Please try again");
}
});
}
else {
alert("Something went wrong with reCaptcha. Please try again!");
}
}
3.2.4. wwwroot\style.css
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
@import url(https://fonts.googleapis.com/css?family=Roboto:300);

.login-page {
width: 360px;
padding: 8% 0 0;
margin: auto;
}

.form {
position: relative;
z-index: 1;
background: rgb(116, 223, 187);
max-width: 360px;
margin: 0 auto 100px;
padding: 30px;
text-align: center;
box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.2), 0 5px 5px 0 rgba(0, 0, 0, 0.24);
}

.form input {
font-family: "Roboto", sans-serif;
outline: 0;
background: #f2f2f2;
width: 100%;
border: 0;
margin: 0 0 15px;
padding: 15px;
box-sizing: border-box;
font-size: 14px;
}

.form button {
font-family: "Roboto", sans-serif;
text-transform: uppercase;
outline: 0;
background: rgb(8, 103, 116);
width: 100%;
border: 0;
padding: 15px;
color: #FFFFFF;
font-size: 14px;
-webkit-transition: all 0.3 ease;
transition: all 0.3 ease;
cursor: pointer;
}

.form button:hover, .form button:active, .form button:focus {
background: #073b44;
}

.container {
position: relative;
z-index: 1;
max-width: 300px;
margin: 0 auto;
}
3.2.5. 後端程式碼
3.2.6. appsettings.json

設定reCaptcha金鑰,api controller 會用到

1
2
3
4
5
6
7
8
9
10
11
12
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"reCaptcha": {
"SecretKey": "6L...Dh"
},
"AllowedHosts": "*"
}
3.2.7. Program.cs

啟動CORS功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//讓前端專案callapi時,會有跨網站存取的議題,請視情況調整,以下完全無限制。
builder.Services.AddCors(options =>
{
options.AddDefaultPolicy(
policy =>
{
policy.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod();
});
});

//啟動cors功能
app.UseCors();

注入httpclient功能

1
2
//後端程式要與googl api溝通,所以要將這個服務注入
builder.Services.AddHttpClient();

完整程式碼如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

//讓前端專案callapi時,會有跨網站存取的議題,請視情況調整,以下完全無限制。
builder.Services.AddCors(options =>
{
options.AddDefaultPolicy(
policy =>
{
policy.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod();
});
});

//後端程式要與googl api溝通,所以要將這個服務注入
builder.Services.AddHttpClient();



var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}

//有使用到wwwroot資料夾,以下兩行就必須使用
app.UseDefaultFiles();
app.UseStaticFiles();

//啟動cors功能
app.UseCors();

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

3.2.8. Controllers\UserController

controllers資料夾底下建立新controller

image-20240120222645961

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;

namespace reCaptchaAPI.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class UserController : ControllerBase
{
private readonly IConfiguration _configuration;
private readonly HttpClient _httpClient;
//注入兩個服務
public UserController(IConfiguration configuration, HttpClient httpClient)
{
_configuration = configuration;
_httpClient = httpClient;
}
//API 名稱為 Captcha
[HttpGet("Captcha")]
public async Task<bool> GetreCaptchaResponse(string userResponse)
{
//取得config中的金鑰
var reCaptchaSecretKey = _configuration["reCaptcha:SecretKey"];

if (reCaptchaSecretKey != null && userResponse != null)
{
//設定金鑰與ajax丟過來的userResponse
var content = new FormUrlEncodedContent(new Dictionary<string, string>
{
{"secret", reCaptchaSecretKey },
{"response", userResponse }
});

//向google 驗證
var response = await _httpClient.PostAsync("https://www.google.com/recaptcha/api/siteverify", content);

//驗證結果
if (response.IsSuccessStatusCode)
{
//第一種寫法,將結果映射到reCaptchaResponse
var result = await response.Content.ReadFromJsonAsync<reCaptchaResponse>();

//第二種寫法,直接取得字串
//var result = await response.Content.ReadAsStringAsync();

return result.Success;
}
}
return false;
}

public class reCaptchaResponse
{
public bool Success { get; set; }

public string[] ErrorCodes { get; set; }
}
}
}

3.3. 測試

image-20240120222741531

image-20240120222809682

image-20240120222827090

image-20240120222852585

4. 參考連結