前后端分离模式下验证码实现

一、前言

验证码,主要用于防刷,特别是注册页面。如果没有防刷机制,攻击者可以通过爬虫等技术,批量注册空虚的用户。

二、正文

前端使用 Vue,并借助 ElementUI 简单美化页面、axios 发送 ajax 请求;
后端使用 SpringBoot,并借助 Kaptcha 生成验证码。

1.思路

①用户进入 web 页面,前端请求后端生成一幅带验证码图片给前端
②后端生成图片的同时,将图片中的正确验证码 rightCode 存到Redis中,key 为用户的ip,value 为正确码,有效时间 几分钟(MySQL 也行,不过不如 Redis 方便高效)
③前端显示后端传来的图片,用户输入请求,将用户的输入码 tryCode 传递给后端;
④后端接受用户的输入码 tryCode,先获取用户的 ip,根据 key = ip,查 value,获取 rightCode,与 tryCode 比对,返回结果。

2.后端核心代码

package com.cun.kaptcha.controller;

import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.util.concurrent.TimeUnit;

import javax.imageio.ImageIO;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.cun.kaptcha.utils.R;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import com.google.code.kaptcha.impl.DefaultKaptcha;

@Controller
@RequestMapping("kaptcha")
public class KaptchaController {

    private final Logger logger = LoggerFactory.getLogger(KaptchaController.class);

    /**
     * 验证码工具
     */
    @Autowired
    DefaultKaptcha defaultKaptcha;

    /**
     * redis工具
     */
    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    /**
     * 生成验证码
     *
     * @param httpServletRequest  获取ip
     * @param httpServletResponse 传输图片
     * @throws Exception
     */
    @RequestMapping("/img")
    public void img(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse)
            throws Exception {
        byte[] captchaChallengeAsJpeg = null;
        ByteArrayOutputStream jpegOutputStream = new ByteArrayOutputStream();
        try {
            String createText = defaultKaptcha.createText();
            // 生产验证码字符串并保存到 Redis 中,ip-rightCode,有效期为 1 小时
            String ip = httpServletRequest.getRemoteAddr();
            logger.info("ip:" + ip + ",rightCode = " + createText);
            redisTemplate.opsForValue().set(ip, createText, 1, TimeUnit.HOURS);
            // 使用生产的验证码字符串返回一个BufferedImage对象并转为byte写入到byte数组中
            BufferedImage challenge = defaultKaptcha.createImage(createText);
            ImageIO.write(challenge, "jpg", jpegOutputStream);
        } catch (IllegalArgumentException e) {
            httpServletResponse.sendError(HttpServletResponse.SC_NOT_FOUND);
            return;
        }
        // 定义response输出类型为image/jpeg类型,使用response输出流输出图片的byte数组
        captchaChallengeAsJpeg = jpegOutputStream.toByteArray();
        httpServletResponse.setHeader("Cache-Control", "no-store");
        httpServletResponse.setHeader("Pragma", "no-cache");
        httpServletResponse.setDateHeader("Expires", 0);
        httpServletResponse.setContentType("image/jpeg");
        ServletOutputStream responseOutputStream = httpServletResponse.getOutputStream();
        responseOutputStream.write(captchaChallengeAsJpeg);
        responseOutputStream.flush();
        responseOutputStream.close();
    }

    /**
     * 校对验证码
     *
     * @param httpServletRequest 获取ip
     * @return
     */
    @ResponseBody
    @RequestMapping("/check/{tryCode}")
    public R check(HttpServletRequest httpServletRequest, @PathVariable String tryCode) {
        String ip = httpServletRequest.getRemoteAddr();
        logger.info("ip:" + ip + ",tryCode = " + tryCode);
        // 从 Redis 中校验
        String rightCode = redisTemplate.opsForValue().get(ip);
        if (rightCode != null && rightCode.equals(tryCode)) {
            return R.ok("校验成功", rightCode);
        }
        return R.error("校验失败");
    }
}

全部代码已经上传至 GitHub:https://github.com/larger5/code-server

3.前端核心代码

<template>
    <div id="app">
        <el-form :inline="true">
            <el-form-item>
                <el-image src="http://localhost/kaptcha/img" onclick="this.src='http://localhost/kaptcha/img?d='+new Date()*1">el-image>
            el-form-item>
            <el-form-item>
                <el-input v-model="code" style="width: 150px">el-input>
            el-form-item>
            <el-form-item>
                <el-button @click="checkCode" type="primary">Checkel-button>
            el-form-item>
        el-form>


    div>
template>

<script>
    export default {
        data() {
            return {
                code: ""
            }
        },
        methods: {
            async checkCode() {
                const {
                    data: res
                } = await this.$http.get(`http://localhost/kaptcha/check/${this.code}`);
                if (res.code === 200) {
                    this.$notify({
                        title: 'It works!',
                        type: 'success',
                        message: '验证码校验正确',
                        duration: 2000
                    })
                } else {
                    this.$notify({
                        title: 'It do not works!',
                        type: 'error',
                        message: '校验失败',
                        duration: 2000
                    })
                }
            }
        }
    }
script>

<style>
    #app {
        font-family: Helvetica, sans-serif;
        text-align: center;
    }
style>

全部代码已经上传至 GitHub:https://github.com/larger5/code-web

4.效果

前后端分离模式下验证码实现_第1张图片

三、结尾

近年来,以往的这一套防刷机制,还是有风险的,比如爬虫可以先把图片爬取下来,通过图片识别API识别出里边 code,再进行操作,依旧是可以通过机器对系统制造垃圾数据。

目前主流防刷机制有手滑、拼图等。

你可能感兴趣的:(Java)