GIF 验证码相对于 JPG 图片验证码来说,要更难破解一些,加大了破解的代价。
从昨天到现在,写了一个小小的 GIF 验证码项目 (中文成语)。
当然,你可以自己修改成字母数字的。我只是单纯的觉得中文验证码的破解代价更高一点~
我在这里生成 GIF 图片的类,用到了国外牛人的三个类,也就是:
AnimatedGifEncoder
LZWEncoder
和 NeuQuant,这三个类。
没办法,谁让自己还没有那个本事写出这样的类呢,只能用别人的,不过挺好用飞,大家可以搜索一下这 3 个类,一下就能搜出源码的。
在这里,我就不贴出这三个类的源码了,需要的,可以在本文最后的项目链接拿整个项目,其中有所有源代码。
本来一开始是写的字母和数字生成的 GIF 验证码,后来还是改成了汉字成语验证码。
在这里,我并没有用数据库来存储成语,因为重点不在哪里,所以就只是建立了一个静态块来先写入成语。
(如果是实际开发,我可能会这样做:
以便于管理员在后台可以添加成语到验证码成语库,以及可以刷新验证码到成语库中,所以,可以在一个请求方法中操作成语。
如果用来 Redis,基本上也是一样,实现同步就行。)
- package cn.hncu.utils;
- import java.awt.*;
- import java.awt.image.BufferedImage;
- import java.io.IOException;
- import java.io.OutputStream;
- import java.util.ArrayList;
- import java.util.List;
- /**
- * Created with IntelliJ IDEA.
- * User: 陈浩翔.
- * Date: 2017/3/6.
- * Time: 下午 8:23.
- * Explain:Gif验证码类
- */
- public class GifCaptcha {
- private Font font = new Font("宋体", Font.BOLD, 20); // 字体
- private int width = 160; // 验证码显示长度
- private int height = 40; // 验证码显示高度
- private String word = ""; // 当前的字符串
- private int delay = 100; // 帧延迟 (默认100)
- private int quality = 10;//量化器取样间隔 - 默认是10ms
- private int repeat = 0; // 帧循环次数
- private int minColor =0;//设置随机颜色时,最小的取色范围
- private int maxColor = 255;//设置随机颜色时,最大的取色范围
- private int right = 0; //设置字符最右边的相对位置---相对原始位置 ,默认为0
- private static java.util.List words = new ArrayList();// 所有成语
- //这里应该去数据库中读取成语,然后存储在内存中
- //在实际开发中,应该是可以在后台中添加成语,以及刷新成语到内存中去!利用访问某个方法来实现
- static {
- words.add("一唱一和");
- words.add("一呼百应");
- words.add("一干二净");
- words.add("一举两得");
- words.add("一落千丈");
- words.add("两面三刀");
- words.add("六神无主");
- words.add("千辛万苦");
- words.add("万无一失");
- words.add("拔刀相助");
- words.add("过时黄花");
- words.add("地动山摇");
- words.add("不可多得");
- words.add("沧海一粟");
- words.add("水泄不通");
- words.add("不可计数");
- }
- /**
- * 空参构造函数
- */
- public GifCaptcha() {
- }
- /**
- * 可以设置验证码宽度,高度的构造函数
- * @param width -验证码宽度
- * @param height -验证码高度
- */
- public GifCaptcha(int width, int height) {
- this.width = width;
- this.height = height;
- }
- /**
- *
- * @param width -验证码宽度
- * @param height -验证码高度
- * @param font -字体
- */
- public GifCaptcha(int width, int height, Font font) {
- this(width, height);
- this.font = font;
- }
- /**
- * @param width -验证码宽度
- * @param height -验证码高度
- * @param font -字体
- * @param delay -帧延迟
- */
- public GifCaptcha(int width, int height, Font font,int delay) {
- this(width, height,font);
- this.delay = delay;
- }
- public Font getFont() {
- return font;
- }
- /**
- * 设置字体
- * @param font
- */
- public void setFont(Font font) {
- this.font = font;
- }
- public int getWidth() {
- return width;
- }
- /**
- * 设置验证码宽度
- * @param width
- */
- public void setWidth(int width) {
- this.width = width;
- }
- public int getHeight() {
- return height;
- }
- /**
- * 设置验证码高度
- * @param height
- */
- public void setHeight(int height) {
- this.height = height;
- }
- public String getWord() {
- return word;
- }
- /**
- * 设置验证码字符
- * @param chars
- */
- public void setWord(String chars) {
- this.word = chars;
- }
- public int getDelay() {
- return delay;
- }
- /**
- * 设置每一帧之间的延迟时间,或改变它的后续帧(适用于最后一帧添加)。
- * @param delay 单位是毫秒
- */
- public void setDelay(int delay) {
- this.delay = delay;
- }
- public int getQuality() {
- return quality;
- }
- /**
- * 设置图像的颜色量化(转换质量 由GIF规范允许的最大256种颜色)。
- * 低的值(最小值= 1)产生更好的颜色,但处理显著缓慢。
- * 10是默认,并产生良好的颜色而且有以合理的速度。
- * 值更大(大于20)不产生显著的改善速度
- * @param quality 大于1
- */
- public void setQuality(int quality) {
- if(quality<1){
- quality=1;
- }
- this.quality = quality;
- }
- public int getRepeat() {
- return repeat;
- }
- /**
- * 设置GIF帧应该播放的次数。
- * 默认是 0; 0意味着无限循环。
- * 必须在添加的第一个图像之前被调用。
- * @param repeat 必须大于等于0
- */
- public void setRepeat(int repeat) {
- if (repeat>=0) {
- this.repeat = repeat;
- }
- }
- public int getRight() {
- return right;
- }
- public void setRight(int right) {
- this.right = right;
- }
- public int getMaxColor() {
- return maxColor;
- }
- public void setMaxColor(int maxColor) {
- this.maxColor = maxColor;
- }
- public int getMinColor() {
- return minColor;
- }
- public void setMinColor(int minColor) {
- this.minColor = minColor;
- }
- /**
- * 给定一个输出流 输入图片
- * @param os
- */
- public void out(OutputStream os) {
- try {
- AnimatedGifEncoder gifEncoder = new AnimatedGifEncoder();// gif编码类
- //生成字符
- gifEncoder.start(os);
- gifEncoder.setQuality(quality);//设置量化器取样间隔
- gifEncoder.setDelay(delay);//设置帧延迟
- gifEncoder.setRepeat(repeat);//帧循环次数
- BufferedImage frame;
- char[] rands = createWordChar();
- Color fontcolor[] = new Color[word.length()];
- for (int i = 0; i < word.length(); i++) {
- fontcolor[i] = getRandomColor(minColor,maxColor);
- }
- for (int i = 0; i < word.length(); i++) {
- frame = graphicsImage(fontcolor, rands, i);
- gifEncoder.addFrame(frame);
- frame.flush();
- }
- gifEncoder.finish();
- } finally {
- try {
- os.close();
- } catch (IOException e) {
- // TODO 异常处理
- e.printStackTrace();
- }
- }
- }
- /**
- * 画随机码图
- *
- * @param fontcolor 随机字体颜色
- * @param strs 字符数组
- * @param flag 透明度使用
- * @return BufferedImage
- */
- private BufferedImage graphicsImage(Color[] fontcolor, char[] strs, int flag) {
- BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
- //或得图形上下文
- Graphics2D g2d=image.createGraphics();
- //Graphics2D g2d = (Graphics2D) image.getGraphics();
- //利用指定颜色填充背景
- g2d.setColor(Color.WHITE);
- g2d.fillRect(0, 0, width, height);
- AlphaComposite ac;
- float y = (height >> 1) + (font.getSize() >> 1) ;// 字符的y坐标
- float m = (width-(word.length()*font.getSize()))/word.length();
- float x = m/2;//字符的x坐标
- g2d.setFont(font);
- for (int i = 0; i < word.length(); i++) {
- ac = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, getPellucidity(flag, i));
- g2d.setComposite(ac);
- g2d.setColor(fontcolor[i]);
- g2d.drawOval(Randoms.num(width), Randoms.num(height), Randoms.num(5,30), 5 + Randoms.num(5,30));//绘制椭圆边框
- g2d.drawString(strs[i] + "",x+(font.getSize()+m)*i+right,y);
- }
- g2d.dispose();
- return image;
- }
- /**
- * 获取透明度,从0到1,自动计算步长
- * @return float 透明度
- */
- private float getPellucidity(int i, int j) {
- int num = i + j;
- float r = (float) 1 / word.length(), s = (word.length() + 1) * r;
- return num > word.length() ? (num * r - s) : num * r;
- }
- /**
- * 生成随机字符数组
- * @return 字符数组
- */
- protected char[] createWordChar() {
- word = words.get(Randoms.num(words.size()));
- return word.toCharArray();
- }
- /**
- * 通过给定范围获得随机的颜色
- * @return Color 获得随机的颜色
- */
- protected Color getRandomColor(int min, int max) {
- if (min > 255) {
- min = 255;
- }
- if (max > 255) {
- max = 255;
- }
- if(min<0){
- min=0;
- }
- if(max<0){
- max=0;
- }
- if(min>max){
- min=0;
- max=255;
- }
- return new Color(min + Randoms.num(max - min), min + Randoms.num(max - min), min + Randoms.num(max - min));
- }
- }
注释没写很多~ 有点懒~
- package cn.hncu.controller;
- import cn.hncu.utils.GifCaptcha;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- import org.springframework.stereotype.Controller;
- import org.springframework.web.bind.annotation.RequestMapping;
- import org.springframework.web.bind.annotation.RequestMethod;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- import javax.servlet.http.HttpSession;
- import java.awt.*;
- import java.io.IOException;
- /**
- * Created with IntelliJ IDEA.
- * User: 陈浩翔.
- * Date: 2017/3/6.
- * Time: 下午 8:26.
- * Explain:演示GIF验证码的控制器
- */
- @Controller
- public class CaptchaController {
- private Logger logger = LoggerFactory.getLogger(CaptchaController.class);
- /**
- * 获取Gif验证码
- * @param response
- */
- @RequestMapping(value = "gifCaptcha",method= RequestMethod.GET)
- public void getGifCaptcha(HttpServletResponse response,HttpServletRequest request){
- //告诉客户端,输出的格式
- response.setHeader("Pragma", "No-cache");
- response.setHeader("Cache-Control", "no-cache");
- response.setDateHeader("Expires", 0);
- response.setContentType("image/gif");
- GifCaptcha gifCaptcha = new GifCaptcha(200,80,new Font("宋体", Font.BOLD, 40),100);
- try {
- gifCaptcha.out(response.getOutputStream());
- logger.info("获取验证码!验证码字符为:"+gifCaptcha.getWord());
- HttpSession session = request.getSession(true);
- //存入Session
- session.setAttribute("captchaWord",gifCaptcha.getWord());
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- @RequestMapping("index")
- public String index() {
- return "index";
- }
- }
- <%--
- Created by IntelliJ IDEA.
- User: 陈浩翔
- Date: 2017/3/6
- Time: 下午 8:24
- To change this template use File | Settings | File Templates.
- --%>
- <%@ page contentType="text/html;charset=UTF-8" language="java" %>
- <html>
- <head>
- <title>演示动态验证码</title>
- <script type="text/javascript">
- var path = "${pageScope.basePath}";
- function changImg() {
- var img = document.getElementById("servletImg");
- var d = new Date();
- var time = d.getTime();//如果没有这个,下面的img.src = path + "gifCaptcha?" + time;不会起作用,因为浏览器的缓存技术,图片可能并不会刷新
- img.src = "";//解决火狐下验证码刷不出的问题
- img.src = path + "gifCaptcha?" + time;
- //?号后面的东西是通过get方式传递的
- }
- </script>
- </head>
- <body>
- 演示动态验证码:
- <a onclick="javascript:changImg();" href="javascript:void(0);">
- <img id="servletImg" src="gifCaptcha" alt="UIFuture验证码"/>
- </a>
- </body>
- </html>
大家其实可以看到,在我点击验证码的时候,有一个小停顿,会显示 alt 的内容,那是因为我在 JS 中,2 次赋值给 img 的 src 属性。
原因是为了解决火狐浏览器显示 GIF 图的一个问题,如果我不加那个 img.src = "";,在刷新验证码 2 次后,验证码 gif 图只显示第一帧!也就是变成了静态图~ 但是接收到的图片其实还是 GIF 动图。
我加 img.src = "";,就只是为了解决火狐上验证码刷新 2 次后会变成静图的问题,该问题在谷歌浏览器,以及 360 浏览器上没有出现!
有知道原因的请评论,谢谢
出问题的是下面这样的情况,在第三次点击图片刷新时 (此时用的是同一张图片,随机图片出现的问题是一样的,也就是只显示 GIF 动图的第一帧图片)(火狐浏览器)
谷歌浏览器,360 浏览器没有出现该问题。
本篇博客涉及到的源码链接:
来源: http://www.bubuko.com/infodetail-1974192.html