在SpringBoot和Vue3中使用Google的reCaptchaV3
Google的RecaptchaV3是一种对用户很友好的验证码模型,接下来从0开始介绍RecaptchaV的使用。
首先是需要去google的官网申请reCaptchaV3(google账号和访问方式请自备,这里不描述)
https://www.google.com/recaptcha/about/
点击右上角的getStarted(顶上那个Admin Console可以进入管理后台)
然后创建项目,名字随便起
选择v3版本然后确定,之后记得保存页面上两个密钥,一个是siteKey(在网站上用),一个后端的key(后端向google请求)
点击设置图标进入设置
添加自己的域名,如果需要在本地测试,记得添加localhost。生产环境请使用新的项目,勿添加localhost。
然后记得滑到底下点保存!不然不会记录,我就被这个卡了一会。
此时google部分的申请就已经完成。
之后是前后端集成。
这里先简单描述下reCaptchaV3工作流程:
1、前端导入google提供的js,这个js会自动分析用户的行为来判断是否机器人,调用该js的方法(需要使用上面的siteKey)会得到一个token,把这个token手动发送给后端
2、后端拿到token后,把该token和后端用的key发给google的服务器来得到分析的结果,结构大致如下:
{
"success": true|false, // whether this request was a valid reCAPTCHA token for your site
"score": number // the score for this request (0.0 - 1.0)
"action": string // the action name for this request (important to verify)
"challenge_ts": timestamp, // timestamp of the challenge load (ISO format yyyy-MM-dd'T'HH:mm:ssZZ)
"hostname": string, // the hostname of the site where the reCAPTCHA was solved
"error-codes": [...] // optional
}
其中success表示请求成功或者失败,score是一个0-1之间的小数,越接近1代表越可能是真人,反之则可能是机器人或脚本
3、根据上面的score得分情况,决定用户的请求可以继续,或被拒绝。
详细的流程,在google的文档中已经有详尽描述
https://developers.google.com/recaptcha/docs/v3?hl=zh-cn
在Vue3中集成:
(注意:本人使用TS环境)
使用vue-recaptcha-v3来进行集成,以下是官网
https://www.npmjs.com/package/vue-recaptcha-v3?activeTab=readme
https://github.com/AurityLab/vue-recaptcha-v3
1、引入
npm install vue-recaptcha-v3
2、配置
main,.ts中添加如下内容(注意顺序)
//导入
import { VueReCaptcha, useReCaptcha } from 'vue-recaptcha-v3'
//创建app和其他内容
app.use(VueReCaptcha,{siteKey:'这里填你自己的',loaderOptions:{
useRecaptchaNet: true,
autoHideBadge: false
}})
这里的loaderoptions是配置项,可以为空,具体配置如下
Name | Description | Type | Default value |
---|---|---|---|
useRecaptchaNet | Due to limitations in certain countries it's required to use recaptcha.net instead of google.com. | boolean | false |
useEnterprise | Uses the enterprise version of the recaptcha api and handles the differences in the response. | boolean | false |
autoHideBadge | Will automatically hide the reCAPTCHA badge. Warning: The usage is only allowed if you follow the offical guide for hiding the badge from Google (see here) | boolean | false |
renderParameters | Will add the given parameters to the reCAPTCHA script. The given object will be converted into a query string and will then be added to the URL. | Object | {} |
explicitRenderParameters | Will set the parameters to the explicit rendering. See here | Object | {} |
其中的useRecaptchaNet会替换Google.com为recaptcha.net 以方便国内访问,请国内的小伙伴务必配置这个。
在Java中进行配置:注意修改包名,另外使用了Okhttp,请引入Okhttp的依赖或改为你用的(如HttpClient)
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.10.0</version>
</dependency>
package com.jessie.expressdeliverysystem.config;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.jessie.expressdeliverysystem.controller.UserController;
import com.jessie.expressdeliverysystem.utils.OkRestClient;
import okhttp3.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import com.jessie.expressdeliverysystem.controller.UserController;
/**
* 校验核心,做了一些修改
* @author JessieLin
* @author sp42 frank@ajaxjs.com
*
*/
@Component
public class GoogleFilter {
@Autowired
GoogleCaptchaConfig cfg;
/**
* 校验表单时候客户端传过来的 token 参数名
*/
public final static String PARAM_NAME = "gRecaptchaToken";
/**
* 谷歌校验 API
*/
private final static String SITE_VERIFY = "https://www.recaptcha.net/recaptcha/api/siteverify";
/**
* 校验
*
* @return 是否通过验证,若为 true 表示通过,否则抛出异常
*/
// public boolean check() {
// return check(UserController.getRequest());
// }
/**
* 校验
*
* @param request 请求对象
* @return 是否通过验证,若为 true 表示通过,否则抛出异常
*/
public boolean check(HttpServletRequest request) {
Boolean res=false;
try{
res= check(request.getParameter(PARAM_NAME));
}catch (IOException e){
e.printStackTrace();
}
return res;
}
/**
*
* @param token
* @return
*/
public boolean check(String token) throws IOException {
if (!cfg.isEnable())
return true;
if (!StringUtils.hasText(token))
throw new SecurityException("客户端缺少必要的参数");
Map<String,String> param=new HashMap<>();
param.put("secret", cfg.getAccessSecret());
param.put("response",token);
OkHttpClient client = new OkHttpClient();
RequestBody body = new FormBody.Builder()
.add("secret", cfg.getAccessSecret())
.add("response",token)
.build();
Request req = new Request.Builder()
.url(SITE_VERIFY)
.post(body)
.build();
//同步请求
Call call = client.newCall(req);
Response response = call.execute();
System.out.println("返回码:"+response.code());
String s=response.body().string();
// System.out.println(s);//?空的?
// Map<String, Object> map = Post.api(SITE_VERIFY, String.format("secret=%s&response=%s", cfg.getAccessSecret(), token.trim()));
JSONObject jsonObject= JSON.parseObject(s);
if (jsonObject.getBoolean("success")) {// 判断用户输入的验证码是否通过
if (jsonObject.getDouble("score") != null) {
// 评分0 到 1。1:确认为人类,0:确认为机器人
double score = jsonObject.getDouble("score");
if (score < 0.5)
throw new SecurityException("验证码不通过,非法请求");
}
return true;
} else {
if ("timeout-or-duplicate".equals(jsonObject.getString("error-codes")))
throw new NullPointerException("验证码已经过期,请刷新");
throw new SecurityException("验证码不正确");
}
}
}
配置文件、
package com.jessie.expressdeliverysystem.config;
import lombok.Getter;
@Getter
public abstract class ClientAccessFullInfo {
/**
* App Id
*/
private String accessKeyId;
/**
* App 密钥
*/
private String accessSecret;
public void setAccessKeyId(String accessKeyId) {
this.accessKeyId = accessKeyId;
}
public void setAccessSecret(String accessSecret) {
this.accessSecret = accessSecret;
}
}
package com.jessie.expressdeliverysystem.config;
/**
* 谷歌验证码配置
*
* @author Frank Cheung<sp42@qq.com>
*/
public class GoogleCaptchaConfig extends ClientAccessFullInfo {
private Boolean enable;
public Boolean isEnable() {
return enable;
}
public void setEnable(Boolean enable) {
this.enable = enable;
}
}
以上部分即为校验的核心,可以通过编写拦截器的形式来触发拦截
package com.jessie.expressdeliverysystem.config;
import java.io.IOException;
import java.lang.reflect.Method;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.alibaba.fastjson.JSONObject;
import com.jessie.expressdeliverysystem.domain.Result;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
/**
*
* @author sp42 frank@ajaxjs.com
*
*/
@Component
@Slf4j
public class GoogleCaptchaMvcInterceptor implements HandlerInterceptor {
@Autowired
GoogleFilter googleFilter;
@Override
public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler) {
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
String httpMethod = req.getMethod();
if (("POST".equals(httpMethod) || "PUT".equals(httpMethod)) && method.getAnnotation(GoogleCaptchaCheck.class) != null) {
// 有注解,要检测
log.info("正在进行reCaptcha检测");
boolean result=googleFilter.check(req);
if(!result){
try{
ServletOutputStream outputStream = resp.getOutputStream();
resp.setContentType("application/json;charset=UTF-8");
outputStream.write(Result.error("人机验证无法通过,请重试").toString().getBytes("UTF-8"));
}catch (IOException e){
e.printStackTrace();
}
}
return result;
}
}
return true;
}
}
package com.jessie.expressdeliverysystem.config;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 防止 Captcha
*
* @author Frank Cheung<sp42@qq.com>
*
*/
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface GoogleCaptchaCheck {
}
最后记得在WebMvcConfig中注册拦截器
@Autowired
GoogleCaptchaMvcInterceptor googleCaptchaMvcInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(googleCaptchaMvcInterceptor);//这里必须是托管给spring的
WebMvcConfigurer.super.addInterceptors(registry);
}
然后就配置完成了,在POST方法上加上@GoogleCaptchaCheck 即可触发拦截器
@PostMapping("/login")
@GoogleCaptchaCheck
public Result Login(User user) {
/*
在这里处理登录的逻辑
*/
}
前端请求
import { useReCaptcha } from "vue-recaptcha-v3";
const recaptcha1=useReCaptcha()
const recaptcha = async () => {
console.log("验证码正在验证。。。")
await recaptcha1?.recaptchaLoaded()
const token = await recaptcha1?.executeRecaptcha('login')
console.log(token)
gRecaptchaToken.value=token as string
return gRecaptchaToken.value;
}
调用时:
async loginHandle() {
const captchaToken= await this.recaptcha() //这里调用recaptcha的方法
let param = new FormData();
param.append("username", username);
param.append("password", password);
param.append("role", role);
param.append("gRecaptchaToken",captchaToken);//然后将返回的token作为参数发给后端
service.post("/api/user/login", param)
.then((res) => {
console.log(res);
if (res.data.code != 200) {
ElMessage.error(res.data.msg)
return;
}
console.log(res.data.data);
sessionStorage.setItem("token", res.data.data.token);
service.defaults.headers.common["token"] = res.data.data.tokenValue;
//做页面跳转
router.push("/home")
})
.catch((err) => {
ElMessage.error(err)
console.log(err)
})
},
好了,这样就完成了一次调用,在后端中若正确接收到参数,会得到如下信息
返回码:200
{
"success": true,
"challenge_ts": "2023-12-16T03:33:26Z",
"hostname": "localhost",
"score": 0.9,
"action": "login"
}
根据score进行判断即可。
该代码后续还有值得完善的地方,等后面有空再说吧。有需要的小伙伴看一下也都知道要怎样修改比较好了。
参考文章:
https://blog.csdn.net/zhangxin09/article/details/123672905
https://blog.51cto.com/jackiehao/7384025
https://juejin.cn/post/6944966415217033247
本文来自博客园,作者:JessieLin,转载请注明原文链接:https://www.cnblogs.com/6543x1/p/17874605.html