react使用谷歌人机验证

在项目中,需要对请求验证,防止被爆破,这里使用的是谷歌的recaptcha-v3。

1.申请谷歌人机验证的api

申请链接,申请完后需要将两个谷歌颁发的key分别写入前,后端的配置环境中,后面会使用.

2.前端部分

前端使用的是vite+CRA框架,先放出配置.

vite全局环境配置(vite.config.js)(环境配置记录,验证请看下面).
javascript 复制代码
import { defineConfig, loadEnv } from 'vite'
import ViteTestConfig from './config/vite.test.config.js'
import ViteBaseConfig from './config/vite.base.config.js'


// 策略模式做一个动态的配置
const envResolver = {
  "build": () => {
    console.log("生产环境")
    return ({ ...ViteTestConfig, ...ViteBaseConfig })
  },
  "serve": () => {
    console.log("开发环境")

    // 另一种写法
    // return Object.assign({}, ViteBaseConfig, ViteDevConfig)
    return ({ ...ViteTestConfig, ...ViteBaseConfig })
  }

}


// https://vitejs.dev/config/
export default defineConfig(
  ({ command, mode }) => {
    const env = loadEnv(mode, process.cwd(), "")
    console.log("env : ", env)

    // 根据不同的环境使用不同的配置文件,注意这个地方的写法,非常的奇特
    return envResolver[command]()
  }
)

这里使用动态配置vite的环境,vite的config文件,当前使用的是本地开发环境使用的是vite.test.config.js,其他环境按这样引用就好了,环境配置比如host,prot,dir等等都在自己需要的环境config中配置就行了.

同时还需要配置环境变量,环境变量使用.env文件,这里我创建了个env文件夹然后将环境变量都放进去了,需要在vite的环境配置中告诉其环境变量文件位置.

javascript 复制代码
import react from "@vitejs/plugin-react"
import { defineConfig } from "vite"
export default defineConfig({
  plugins: [react()],
  envDir: 'src/env',
})

同时也分各种不同的环境变量配置,格式为:.env.(环境名称) 例如:..env.development

而.env则是默认的环境变量,也可以理解为全局变量,不管你加不加载都会读取其中的变量.

而vite中的变量命名必须以 VITE_ 开头,否则不会读取

例子:

VITE_TEST_BASE_URL="http://127.0.0.1:8080/api"

在react中读取变量(react数据共享)

react讲究一个纯函数,所以变量声明只能在其组件中使用,这就有一个问题,当我们使用主题或者存储用户token的时候就不能在所有组件中共享,如果一个个传递下去非常麻烦,所以需要使用到react提供的useContext.

1.创建一个context载体组件

javascript 复制代码
// AppContext.js
import React from 'react'

const AppContext = React.createContext()

export default AppContext

2.在父组件中使用

javascript 复制代码
function App() {
// 主题模式
  const [styleMode, setStyleMode] = React.useState('light')


//改变页面主题
  const changeMode = () => {
    const newMode = styleMode === 'dark' ? 'light' : 'dark'
    setStyleMode(newMode)
    localStorage.setItem('theme', newMode)
  }

   //主题
  const customTheme = createTheme({
    palette: {
      mode: styleMode,
    },
  })

<ThemeProvider theme={customTheme}>
        <AppContext.Provider
          value={{ mode: styleMode, changeMode }}>
          {ElementRouter}
        </AppContext.Provider>
    </ThemeProvider>
}

使用该方式可以将变量在被包裹的组件中使用,同时也可以传递函数,对父组件中的数据进行修改。

3.子组件调用

直接使用useContext(载体组件名)即可获得变量

//获取全局变量

const { mode, changeMode } = useContext(AppContext)
//获取环境变量

const variable= import.meta.env.定义的环境变量名

在前端引入recaptcha v3

这里使用封装好的组件引入,npm安装

npm install react-google-recaptcha-v3

v2的话需要自己去找一下引入方式,此方法只适合v3.

引入后在需要引入谷歌验证的父组件上对子组件进行包裹,这里我直接放在APP组件上,直接全部使用了.

javascript 复制代码
function App{

 return (
    <ThemeProvider theme={customTheme}>
      <GoogleReCaptchaProvider
        reCaptchaKey={import.meta.env.VITE_GOOGLE_CAPCHAT_KEY}
        //国内谷歌验证
        useRecaptchaNet={true}>
        <AppContext.Provider
          value={{ mode: styleMode, baseUrl: baseUrl, changeMode }}>
          {ElementRouter}
        </AppContext.Provider>
      </GoogleReCaptchaProvider>
    </ThemeProvider>
  )
}

key就是你申请到的前端key(client),useRecaptchaNet一定要开,不开的话会去访问谷歌的verifty,局域网内是无法访问的.

使用的话v3使用的是一个无感验证,不需要用户去进行图片或者人机验证点击,所以直接封装一个验证逻辑后放入代码中就好了(比如提交表单的时候或者点击某些按钮的时候等等)

javascript 复制代码
import { requestSlimpePost } from "./RequestUtils"

async function checkRobot (executeRecaptcha, baseUrl) {
  //人机验证不可用情况
  if (!executeRecaptcha) {
    alert('人机验证不可用,请检查网络!')
    return false
  }

  //获得验证token
  const capChatToken = await executeRecaptcha()

  //获取token失败
  if (!capChatToken) {
    alert('人机验证失败!')
    return false
  }

  //封装成json数据
  const data = {
    capChatToken: capChatToken,
  }
  const jsonData = JSON.stringify(data, null, 2)


  //获取返回数据
  const res = await requestSlimpePost(baseUrl + '/verify/recapchat', jsonData)
  if (res.msg != 'ok' || res.code != 200) {
    alert('人机验证失败!')
    console.log(res)
    return false
  }

  return true
}

export { checkRobot }

其中requestSlimpePost是我自己封装的请求工具,就使用fetch就不过多介绍了.

javascript 复制代码
 const { executeRecaptcha } = useGoogleReCaptcha()
  const { baseUrl } = React.useContext(AppContext)

  const handleSubmit = async (event) => {
    event.preventDefault()

    //获取当前登录数据
    const userInputData = new FormData(event.currentTarget)
    const userName = userInputData.get('userName')
    const passwd = userInputData.get('passwd')

    if (!userName) {
      alert('用户名不能为空')
      return
    }

    if (!passwd) {
      alert('密码不能为空')
      return
    }

    const notRobot = await checkRobot(executeRecaptcha, baseUrl)
    if (!notRobot) return

  }

先通过封装的包中获取executeRecaptcha这个对象,然后通过调用其中函数向谷歌验证申请验证令牌,拿到后再通过后端携带该令牌去进行评测.

3.后端部分

在前端获取验证令牌后,需要向后端申请验证,后端则向谷歌申请该次人机验证的评分,并进行逻辑操作.

java 复制代码
/**
 * @author Vermouth
 * @ClassName: ReCapChatManager
 * @Description: 人机校验管理器
 * @Date 2024/4/10 11:05
 * @Version: V1.0
 */
@Component
public class ReCapChatManager {
    // 请求地址
    private static final String SITEVE_RIFY = "https://www.recaptcha.net/recaptcha/api/siteverify";
    private static OkHttpClient httpClient = new OkHttpClient();

    @Autowired
    private ReCapIpManager reCapIpManager;

    // 从配置文件中读取到后端key
    @Value("${recaptcha.server-key}")
    private String serverKey;


    /**
     * 人机校验
     *
     * @param request 请求包装类
     * @return true / false
     * @throws IOException
     */
    public boolean robotCheck(HttpServletRequest request) throws IOException {

        //请求的IP
        String remoteAddr = RequestUtils.getClientIp(request);
        String requestJson = StringUtils.bufferToString(request.getReader());
        JsonObject requestObj = JsonParser.parseString(requestJson).getAsJsonObject();

        //异常的请求也驳回
        if (null == requestObj) {
            this.addFreezeIp(remoteAddr);
            return false;
        }

        //请求体设置
        RequestBody formBody = new FormBody.Builder().add("secret", this.serverKey)  //服务端key
                .add("response", requestObj.get("capChatToken").getAsString())   //客户端提交的token
                //.add("remoteip", remoteAddr)  //客户的ip地址,不是必须的参数。
                .build();

        //请求头设置
        Headers reqHeaders = new Headers.Builder().add(HttpHeaders.CONTENT_TYPE, "application/x-www-form-urlencoded;charset=UTF-8").add(HttpHeaders.CONTENT_LENGTH, String.valueOf(formBody.contentLength())).build();


        //装入请求中
        Request reRequest = new Request.Builder().url(SITEVE_RIFY).headers(reqHeaders).post(formBody).build();

        Call call = httpClient.newCall(reRequest);
        String responseData = null;

        try {
            //发起请求
            Response googleResponse = call.execute();

            //检查是否获取到响应体
            if (null == googleResponse.body()) throw new IOException();
            responseData = googleResponse.body().string();

        } catch (IOException e) {
            throw new SystemException(ExceptionStatus.OKHTTP_CALL_EXCEPTION_);
        }


        JsonObject jsonObject = JsonParser.parseString(responseData).getAsJsonObject();

    //TODO 后续有了服务器还需接入Cloudflare!!!!

        // 是否执行成功
        if (!jsonObject.get("success").getAsBoolean()) {

            // 在失败的情况下,获取到异常状态码
            JsonArray errorCodes = jsonObject.get("error-codes").getAsJsonArray();
            this.addFreezeIp(remoteAddr);
            return false;
        }

        //获取评分
        double score = jsonObject.get("score").getAsDouble();
        if (score < 0.5) {

            // 如果低于0.5分,服务不接受该请求
            this.addFreezeIp(remoteAddr);
            return false;
        }

        return true;
    }


    /**
     * 冻结ip操作
     *
     * @param ipAddress 需冻结ip
     */
    public void addFreezeIp(String ipAddress) {
        reCapIpManager.freezeIp(ipAddress);
    }


}

需要注意的是请求地址,请求地址同样也必须是国内的谷歌验证api,否则访问不到.

一般来说打分都是在0.9,我自己测试的都是在0.9,可以根据自己的情况来调整判断人机的分数.

相关推荐
持久的棒棒君7 分钟前
ElementUI 2.x 输入框回车后在调用接口进行远程搜索功能
前端·javascript·elementui
2401_8572979117 分钟前
秋招内推2025-招联金融
java·前端·算法·金融·求职招聘
undefined&&懒洋洋1 小时前
Web和UE5像素流送、通信教程
前端·ue5
winkee3 小时前
在 git commit 中使用 gpg key 进行签名
架构·前端框架·代码规范
大前端爱好者3 小时前
React 19 新特性详解
前端
小程xy3 小时前
react 知识点汇总(非常全面)
前端·javascript·react.js
随云6323 小时前
WebGL编程指南之着色器语言GLSL ES(入门GLSL ES这篇就够了)
前端·webgl
随云6323 小时前
WebGL编程指南之进入三维世界
前端·webgl
无知的小菜鸡3 小时前
路由:ReactRouter
react.js
寻找09之夏4 小时前
【Vue3实战】:用导航守卫拦截未保存的编辑,提升用户体验
前端·vue.js