引言:

在学习springMVC 和Spring Security中,总有一个服务,叫做验证码,每次我自己写这个验证码的时候,最后还要在测试,而且特别的不友好,甚至我自己都分不清那个字母,当然Java里面有个很优秀的滑块验证码比如这个大佬开发的tianai-captcha
总是给人一种不是很优雅,我自己是人类,为什么还要进行验证码,而且这种验证往往经过训练的ai更容易比人类做的好,这就失去了验证码的意义CAPTCHA,Cloud Flare就做了一个无需进行让你输入字符或者滑动以及选择图片的验证码,如果需要更多了解你可以访问CF的官方介绍CloudFlare-Turnstile

需要准备

  1. 一个Cloud Flare账号
  2. 一个域名(不需要接入Cloud Flare)

    流程

CloudFlare以下简称CF, 官网的配置文件已经写的很清楚
Turnstile

  1. 用户打开受验证码保护的网页,先会和CF的验证码服务器进行通讯拿到,JWT令牌(令牌有效300秒)
  2. 用户会把提交内容以及CF生成的令牌发送到服务器,
  3. 服务器会拿着CF令牌进行和CF验证码服务器通讯,拿到JSON数值

快速使用

  1. 登录你的CF账号会在仪表盘的左侧看到Turnstile
  2. 点击添加
  3. image-20230328110719966

我一般是选择托管,由cf来决定用户需不需要进行交互一下,

  1. 注册完毕之后我们会得到两个值一个是站点密钥,一个是密钥,密钥是用来进行服务端和CF通讯的,而站点密钥是放在被保护的资源上的。

按照CF文档的客户端配置我这里就那官方的例子来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- 引用CF的验证码js -->
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
<title>Document</title>
</head>

<body>
<form id="form" action="/login" method="POST">
<input id="username" type="text" placeholder="username" name="username" />
<input id="password" type="password" placeholder="password" name="password" />
<!-- 在data-sitekey中需要填写站点密钥 -->
<div class="cf-turnstile" data-sitekey="0x4AAAAAAACzpeJEUBQ4rBi-" data-callback="javascriptCallback"></div>
</form>
<button id="button" type="submit" value="Submit">Log in</button>
</body>
<script>

用户单击的登陆之后我们就能在后端拿到用户的账号密码以及CF的令牌。我们会在后端拿到一个名为:cf-turnstile-response的键值对,这里面存放的是cf的令牌。

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
public String test(String req) throws IOException {
//secret 是填写密钥
String secret = "";
//url是默认的地址无需更改
URL url = new URL("https://challenges.cloudflare.com/turnstile/v0/siteverify");
HttpURLConnection httpConn = (HttpURLConnection) url.openConnection();
httpConn.setRequestMethod("POST");
//请求类型cf 请求仅支持 application/x-www-form-urlencoded 或者是 application/JSON
httpConn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");

httpConn.setDoOutput(true);
OutputStreamWriter writer = new OutputStreamWriter(httpConn.getOutputStream());
writer.write("secret="+secret+"&response="+req);
writer.flush();
writer.close();
httpConn.getOutputStream().close();

InputStream responseStream = httpConn.getResponseCode() / 100 == 2
? httpConn.getInputStream()
: httpConn.getErrorStream();
Scanner s = new Scanner(responseStream).useDelimiter("\\A");
String response = s.hasNext() ? s.next() : "";
System.out.println(response);
return response;
}

上述是我们拿到用户的cf-turnstile-response值然后和cf通讯返回的response就是CF给我们的JSON。

根据官方文档:

如果验证成功,响应应类似于以下内容:

1
2
3
4
5
6
7
8
{
"success": true,
"challenge_ts": "2022-02-28T15:14:30.096Z",
"hostname": "example.com",
"error-codes": [],
"action": "login",
"cdata": "sessionid-123456789"
}
  • challenge_ts是解决挑战时的 ISO 时间戳。
  • hostname是为其提供质询的主机名。
  • action是传递给客户端小部件的客户小部件标识符。这用于区分在分析中使用相同站点密钥的小部件。它的完整性受到攻击者修改的保护。建议验证操作是否与预期值匹配。
  • cdata是传递给客户端小部件的客户数据。客户可以使用它来传达状态。它的完整性受到攻击者修改的保护。
  • error-codes是发生的错误列表。

如果验证失败,响应应类似于以下内容:

1
2
3
4
5
6
{
"success": false,
"error-codes": [
"invalid-input-response"
]
}

success通过将属性设置为 来指示验证错误false。提供了错误代码列表以指示响应无法验证的原因。响应还可能包含其他字段,具体取决于 Turnstile siteverify 是否能够成功或不成功地解析响应。

错误代码

错误代码 描述
missing-input-secret 未传递秘密参数。
invalid-input-secret 秘密参数无效或不存在。
missing-input-response 未传递响应参数。
invalid-input-response 响应参数无效或已过期。
bad-request 该请求被拒绝,因为它格式不正确。
timeout-or-duplicate 响应参数之前已经过验证。
internal-error 验证响应时发生内部错误。可以重试该请求。

根据以上信息我们就可以判断并且,接收cf的保护了。

引用

CF Turnstile 开发文档
CloudFlare-Turnstile介绍