使用Springboot集成Kaptcha实现简单的登录验证

1,519 阅读4分钟

Kaptcha简介

  kaptcha是一个相对简单(较旧)的验证码生成工具,通过其给定的配置,可以轻松的实现自己想要的验证码格式。

引入Kaptcha

在pom.xml文件中导入此工具包

<dependencies>
<!--.......-->
 <dependency>
     <groupId>com.github.penggle</groupId>
     <artifactId>kaptcha</artifactId>
     <version>2.3.2</version>
  </dependency>
  <!--.......-->
  <dependencies>

自定义验证码样式

新建Config包,并在里面创建一个KapchaConfig类用来自定义你的验证码样式

@Component
public class KaptchaConfig {
    @Bean
    public DefaultKaptcha getDefaultKaptcha(){
        com.google.code.kaptcha.impl.DefaultKaptcha defaultKaptcha = new com.google.code.kaptcha.impl.DefaultKaptcha();
        Properties properties = new Properties();
        properties.put("kaptcha.border", "no");
        properties.put("kaptcha.textproducer.font.color", "black");
        properties.put("kaptcha.image.width", "200");
        properties.put("kaptcha.image.height", "50");
        properties.put("kaptcha.textproducer.font.size", "25");
        properties.put("kaptcha.session.key", "verifyCode");
        properties.put("kaptcha.textproducer.char.space", "5");
        Config config = new Config(properties);
        defaultKaptcha.setConfig(config);

        return defaultKaptcha;
    }
}

此处配置的类可参考下方的配置表格:

常量描述默认值
kaptcha.border图片边框,合法值:yes , noyes
kaptcha.border.color边框颜色,合法值: r,g,b (and optional alpha) 或者 white,black,blue.black
kaptcha.border.thickness边框厚度,合法值:>01
kaptcha.image.width图片宽200
kaptcha.image.height图片高50
kaptcha.producer.impl图片实现类com.google.code.kaptcha.impl.DefaultKaptcha
kaptcha.textproducer.font.size文本实现类com.google.code.kaptcha.text.impl.DefaultTextCreator
kaptcha.textproducer.font.size字体大小40px.
kaptcha.textproducer.font.color字体颜色,合法值: r,g,b 或者 white,black,blue.black
kaptcha.textproducer.char.space文字间隔2
kaptcha.noise.impl干扰实现类com.google.code.kaptcha.impl.DefaultNoise
kaptcha.noise.color干扰 颜色,合法值: r,g,b 或者 white,black,blue.black
kaptcha.obscurificator.impl图片样式: 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpycom.google.code.kaptcha.impl.WaterRipple
kaptcha.background.impl背景实现类com.google.code.kaptcha.impl.DefaultBackground
kaptcha.background.clear.from背景颜色渐变,开始颜色light grey
kaptcha.background.clear.to背景颜色渐变, 结束颜色white
kaptcha.textproducer.char.length验证码长度5

编写CommonController接口用于返回验证码

在这里,我们以流的形式传输验证码内容给到接口:

@Controller
public class CommonController {

    @Autowired
    private DefaultKaptcha mycaptcha;


    @GetMapping("/common/kaptcha")
    public void defaultKaptcha(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
        byte[] captchaOutputStream = null;
        ByteArrayOutputStream imgOutputStream = new ByteArrayOutputStream();
        try {
            //生成验证码
            String verifyCode = mycaptcha.createText();
            //生产验证码字符串并保存到session中
            //这里埋了个坑,坑的内容是getSession和getServletContext作用域的区别
            httpServletRequest.getSession().setAttribute("verifyCode", verifyCode);
            //向客户端写出
            BufferedImage challenge = mycaptcha.createImage(verifyCode);
            ImageIO.write(challenge, "jpg", imgOutputStream);
        } catch (IllegalArgumentException e) {
            httpServletResponse.sendError(HttpServletResponse.SC_NOT_FOUND);

        }
            captchaOutputStream = imgOutputStream.toByteArray();
            httpServletResponse.setHeader("Cache-Control", "no-store");
            httpServletResponse.setHeader("Pragma", "no-cache");
            httpServletResponse.setDateHeader("Expires", 0);
            httpServletResponse.setContentType("image/jpeg");
            ServletOutputStream outputStream = httpServletResponse.getOutputStream();
            outputStream.write(captchaOutputStream);
            try {
                outputStream.flush();
            }finally {

                outputStream.close();
            }
    }

}

接口测试

用Postman请求 http:localhost:8080/common/kaptcha 查看验证码的生成效果:

在前端页面中显示二维码图片

要接收后端传来的验证码非常简单,仅需要一个img标签请求其 /common/kaptcha 接口即可生成验证码图片。

<form>
    <div class="group">
        <input type="text" v-model="verifyCode" ><span class="highlight"></span><span class="bar"></span>
        <label>验证码</label>
        <!--请求其接口-->
        <img src="http://localhost:8080/common/kaptcha"
                    onclick="this.src='http://localhost:8080/common/kaptcha?d='+new Date()*1">
    </div>
    <button type="button" class="button buttonBlue" v-on:click="loginCheck">登录
        <div class="ripples buttonRipples"><span class="ripplesCircle"></span></div>
    </button>
</form>

其效果图如下:

实现对验证码一致性的匹配

既然在前端已经生成了验证码,那么剩下最后一步——如何判断前后端的验证码都是相同的呢?我们可以在另外一个Controller实现一个简单的判断。用@RequestParam接收前端传来的参数,并在if中判断用户在页面输入的验证码是否和后台生成的验证码相匹配,并以Json的对象返回给前端。

//LoginController
@RequestMapping("/login")
public JSONObject login(@RequestParam String account, @RequestParam String password ,
                        @RequestParam("verifyCode")String FrontverifyCode,
                        HttpServletRequest httpServletRequest){
   String kaptchaCode=(String)httpServletRequest.getSession().getAttribute("verifyCode");
    System.out.println("生成的验证码:"+kaptchaCode);
    System.out.println("接收的验证码:"+FrontverifyCode);
   if (StringUtils.isEmpty(kaptchaCode)||!FrontverifyCode.equals(kaptchaCode)){
       System.out.println("接受的验证码为:"+FrontverifyCode);
       jsonObject = resultCode.getJson("false", "验证码错误");
   }

这个验证码判断思路是这样的:首先在LoginController中用@RequestParam来接收前端传来的账号密码与验证码,接下来通过HttpServletRequest的getAttribute的方法获取CommonController生成的验证码。接下来用将前端拿到的FrontverifyCodekaptchaCode做比较,如果不放心,可以通过System.out在此方法中打印分别打印这两个验证码出来康康是否生成了验证码以及是否能从前端拿到验证码。 ......但我发现事实好像并没有那么简单。 怎么LoginController获取从CommonController的验证码为null?难道是这个接口没有生成验证码还是生成了但没有正确的传过来?于是我便在CommonController也打印下这个验证码。

可以看到,控制台是打印了CommonController的验证码,那么,问题就可能出现在传值上了。看看这个Controller是怎么传递值的:

  httpServletRequest.getSession().setAttribute("verifyCode", verifyCode);

我们知道Servlet可以通过Request,Session,getServletContext来存储对象,而这三者的作用域也是不同的,就Request 而言,其保存的内容仅在下一个request对象中可以得到。 而 Session 它是一个会话范围,相当于一个局部变量,从Session第一次创建到关闭,数据都会保存起来,每一个客户端都会生成有一个Session,只要Session没有关闭和超时,用户便可一直访问。 servletContext 它代表了servlet环境的上下文,相当于一个全局变量,即只要某个web应用在启动中,这个对象就一直都有效的存在,所以它的范围是最大的,存储的数据可以被所有用户使用,只要服务器不关闭,数据就会一直都存在。
因此在比较其三者作用域的差异后,我们可以尝试下用getServletContext来看看能不能正确的传递想要的参数。 CommonController

//替换为
httpServletRequest.getServletContext().setAttribute("verifyCode", verifyCode);

LoginController

//替换为
 String kaptchaCode=(String)httpServletRequest.getServletContext().getAttribute("verifyCode");

接着启动Springboot,在前端输入验证码,查看后台的输出内容,可以看到LoginController成功的接收到来自另一个Controller传来的验证码了,匹配成功!