1 验证码
1.1 什么是验证码?
我搜索百度,从百度百科中拿出来一段话:
验证码(CAPTCHA)是“Completely Automated Public Turing test to tell Computers and Humans Apart”(全自动区分计算机和人类的图灵测试)的缩写,是一种区分用户是计算机还是人的公共全自动程序。可以防止:恶意破解密码、刷票、论坛灌水,有效防止某个黑客对某一个特定注册用户用特定程序暴力破解方式进行不断的登陆尝试,实际上用验证码是现在很多网站通行的方式,我们利用比较简易的方式实现了这个功能。这个问题可以由计算机生成并评判,但是必须只有人类才能解答。由于计算机无法解答CAPTCHA的问题,所以回答出问题的用户就可以被认为是人类。
1.2 验证码的历史
验证码这个词最早是在2002年出现在卡内基梅隆大学。
1.3 验证码的作用
防止恶意破解密码、刷票、论坛灌水、刷页。
1.4 验证码的分类
验证码整体来说分为五类,他们分别是
- 静态图片内容验证码:一般都是输入一些随机码,形式上还可以有计算
- Gif动画验证码:动态展示一些随机码,形式上还可以有计算
- 手机短信验证码:发送短信随机验证码
- 手机语音验证码:语音验证码,也可以让你读出一些随机码或计算结果
- 视频验证码:点击视频中的一些随机码,计算结果
了解了验证码之后,我们开始手工实现一些验证码,当然你也可以使用一些已经封装好的工具来实现这个功能,例如Hutool、Google、Baidu、阿里等大佬们封装好的一些工具类直接实现验证码。
2 实现一个验证码
如果我们想要实现一个验证码,那么就得先要了解下验证码的实现原理以及代码实现。
2.1 验证码的原理
网页之间实现验证码大体上有如下步骤:
1)生成一个随机数
2)将随机数写入图片
3)将图片返回到网页
4)用户获取到图片信息,输入图片内容
5)用户提交内容,服务端验证内容的准确性
整体的流程,使用一张图可能会更形象些,大家可以在这图里面体会下。
这里简单使用“静态图片内容验证码”来作为一个实验例子,来实现我们需要的验证码功能。
2.1 生成一个随机码
生成一个随机码的步骤大体如下:
1)创建一个类RandImagesVerifCode
2)编写一个名叫randomString(String baseString, int length)的方法
3)测试实验
完整的代码如下:
packagecom.liuyc.tooljdk.image; importjava.util.concurrent.ThreadLocalRandom; /*** <p> Picture Verification Code </p>** @author Aion.Liu* @version v1.0.0* @description TODO* @since 2022/9/1 22:24*/publicclassRandImagesVerifCode { publicstaticfinalStringBASE_NUMBER="0123456789"; publicstaticvoidmain(String[] args) { StringresultCode=RandImagesVerifCode.randomString(BASE_NUMBER, 4); System.out.println(resultCode); } /*** 生成一个随机数* @param baseString 指定的源中获取随机数* @param length 获取随机数的个数* @return*/publicstaticStringrandomString(StringbaseString, intlength) { if (baseString==null||baseString=="") { return""; } else { StringBuildersb=newStringBuilder(length); if (length<1) { length=1; } intbaseLength=baseString.length(); for (inti=0; i<length; ++i) { intnumber=ThreadLocalRandom.current().nextInt(baseLength); sb.append(baseString.charAt(number)); } returnsb.toString(); } } // 将验证码写入到redispublicbooleanset(Stringkey, Objectvalue, longtime) { try { if (time>0L) { // this.redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS); } else { // this.set(key, value); } returntrue; } catch (Exceptionvar6) { var6.printStackTrace(); returnfalse; } } }
分步骤解析:
1、public static final String BASE_NUMBER = "0123456789";
这里是指定一个数据源头,也就是从这里面随机多少个数字出来。关于数据源头,这里可以任意指定。例如我们也可以指定字母数字组合
public static final String BASE_CHAR_NUMBER = "abcdefghijklmnopqrstuvwxyz0123456789";
2、public static String randomString(String baseString, int length)
这里是生成随机数的方法,有两个参数。参数的用途已经写在了代码中。长度我们一般使用4~5位,其他少了也不好,多了又太过于麻烦。
3、public boolean set(String key, Object value, long time)
将验证码写入到redis,这是redis的key以及值,当然,还需要设置这个验证码的时效,过期之后这个验证码就不可以再使用,需要我们重新获取。
2.2 将验证码写入一张图片
这个步骤比较简单
1)生成一张图片,将验证码写入图片
2)将图片写入到流中并返回
publicstaticvoidmain(String[] args) throwsIOException { StringresultCode=RandImagesVerifCode.randomString(BASE_NUMBER, 4); System.out.println(resultCode); Stringbase64=RandImagesVerifCode.produceImage(resultCode); System.out.println(base64); } publicstaticStringproduceImage(StringresultCode) throwsIOException { /*** 定义图形大小*/finalintWIDTH=105; /*** 定义图形大小*/finalintHEIGHT=35; /*** 定义干扰线数量*/finalintCOUNT=200; /*** 干扰线的长度=1.414*lineWidth*/finalintLINE_WIDTH=2; /*** 生成一张图片,将结果写入到图片*/// 在内存中创建图象finalBufferedImageimage=newBufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB); // 获取图形上下文finalGraphics2Dgraphics= (Graphics2D) image.getGraphics(); // 设定背景颜色graphics.setColor(Color.WHITE); graphics.fillRect(0, 0, WIDTH, HEIGHT); // 设定边框颜色graphics.drawRect(0, 0, WIDTH-1, HEIGHT-1); finalRandomrandom=newRandom(); // 随机产生干扰线,使图象中的认证码不易被其它程序探测到for (inti=0; i<COUNT; i++) { finalRandomrandomColor=newRandom(); finalintr=150+randomColor.nextInt(50); finalintg=150+randomColor.nextInt(50); finalintb=150+randomColor.nextInt(50); graphics.setColor(newColor(r, g, b)); // 保证画在边框之内finalintx=random.nextInt(WIDTH-LINE_WIDTH-1) +1; finalinty=random.nextInt(HEIGHT-LINE_WIDTH-1) +1; finalintxl=random.nextInt(LINE_WIDTH); finalintyl=random.nextInt(LINE_WIDTH); graphics.drawLine(x, y, x+xl, y+yl); } // 取随机产生的认证码for (inti=0; i<resultCode.length(); i++) { // 设置字体颜色graphics.setColor(Color.BLACK); // 设置字体样式graphics.setFont(newFont("Times New Roman", Font.BOLD, 24)); // 设置字符,字符间距,上边距graphics.drawString(String.valueOf(resultCode.charAt(i)), (23*i) +8, 26); } // 图象生效graphics.dispose(); /*** 将图片写图图片流中*/ByteArrayOutputStreambyteStream=newByteArrayOutputStream(); //写入流中ImageIO.write(image, "JPEG", byteStream); //转换成字节byte[] bytes=byteStream.toByteArray(); //转换成base64串Stringbase64=Base64.getEncoder().encodeToString(bytes).trim(); //删除 \r\nbase64=base64.replaceAll("\n", "").replaceAll("\r", ""); return"data:image/jpg;base64,"+base64; }
执行后的结果如下:
4408
data:image/jpg;base64,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
我们可以使用任意编写一个Html,然后将返回的数据写入img标签的src中,
<htmllang="en"><head><metacharset="UTF-8"><title>Title</title></head><body><imgsrc="data:image/jpg;base64,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"/></body></html>
打开网页后调试下结果如下结果。
现在,我们便完成了网页验证码的开发以及回显示到网页中啦。