Java Web 项目关于 Kaptcha 验证码的生成与使用

Web 端中,为了防止恶意的流量攻击或者为了防止自动化提交,都会在页面中引入验证码。
验证码一般是一些加入了干扰线,做了模糊处理的字符图片,可能是中文、字母、数字等。现在一些网站还加入了语音验证或者手机验证。

验证码实现流程如下:

  1. Web 请求验证码图片
  2. 后台生成随机字符,例如 ABCD,存入 Session 或 Cache
  3. 后台将 ABCD 做模糊处理,并以图片形式返回给前端
  4. 前端提交表单时,将验证码一并带上
  5. 后台将前端提交的验证码与存在 Session 或 Cache 中的验证码比较。如果一致,则成功。

在实践中,可以采用自己绘制验证码图片,原理就是在图片上增加干扰线,干扰点,变形等处理。可以参考这篇博文 java web项目生成验证码的解决方案

引入 Kaptcha 库

以下为 Maven 代码

<!-- Kaptcha验证码框架 -->
<dependency>
    <groupId>com.github.axet</groupId>
    <artifactId>kaptcha</artifactId>
    <version>0.0.9</version>
</dependency>

或者去 Google Code 下载,地址为 https://code.google.com/archive/p/kaptcha/

Kaptcha 使用

Kapthca 使用可以有以下两种方法,一种是直接请求图片,验证码写在 Session 中;另一种是作为 WebService ,由前端主动请求。

通过 img 标签请求

通过 img 标签,直接获取验证码图片,这个场景适合 JSP 工程这种前后端不完全分离的,或者仅使用未禁止 Session 和 Cookie 的浏览器时。

后台 web.xml 配置

在根目录下增加 servlet 和 servlet-mapping

<!-- 登陆验证码 Kaptcha -->
<servlet>
    <servlet-name>Kaptcha</servlet-name>
    <servlet-class>com.google.code.kaptcha.servlet.KaptchaServlet</servlet-class>
    <!-- 验证码各项配置 -->
    <init-param>
        <param-name>kaptcha.border</param-name>
        <param-value>no</param-value>
    </init-param>
    <init-param>
        <param-name>kaptcha.textproducer.font.color</param-name>
        <param-value>red</param-value>
    </init-param>
    <init-param>
        <param-name>kaptcha.textproducer.char.space</param-name>
        <param-value>3</param-value>
    </init-param>
</servlet>
<servlet-mapping>
    <servlet-name>Kaptcha</servlet-name>
    <!-- img src 指定格式 -->
    <url-pattern>/kaptcha.jpg</url-pattern>
</servlet-mapping>

前端 img 请求

只需要一行代码即可。

注意,此处 kaptcha.jpg 要与 web.xml 中 servlet-mapping 中指定的名字一致。

![](http://localhost:7010/kaptcha.jpg)

后台验证

**Kaptcha 默认会将生成的验证码写入 Session ,因此这需要前端浏览器允许记录 Cookies **,否则可能出现 SessionId 不一致使得验证码无法正确校验。在将 Html5 嵌入 iOS App 时,由于内嵌浏览器对 Cookie 做了严格的限制,使得无法正确校验,才转向了第二种方法。
因为验证码被记录在 Session 中,因此 Web 端从 Session 中取出即可,至于如何获得 Session ,视不同项目而定。一般 JSP 项目用这种方法,其校验方法如下::

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
    <head>
        <%@ page language="java" contentType="text/html; charset=UTF-8" %>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>Kaptcha Example</title>
    </head>
    <body>
        Enter in the <a href="http://code.google.com/p/kaptcha/">Kaptcha</a> to see if it matches what is stored in the session attributes.
        <table>
            <tr>
                <td>![](kaptcha.jpg)</td>
                <td valign="top">
                    <form method="POST">
                        <br>sec code:<input type="text" name="kaptchafield"><br />
                        <input type="submit" name="submit">
                    </form>
                </td>
            </tr>
        </table>
        <%
            String c = (String)session.getAttribute(com.google.code.kaptcha.Constants.KAPTCHA_SESSION_KEY);
            String parm = (String) request.getParameter("kaptchafield");
            out.println("Parameter: " + parm + " ? Session Key: " + c + " : ");
            if (c != null && parm != null) {
                if (c.equals(parm)) {
                    out.println("<b>true</b>");
                } else {
                    out.println("<b>false</b>");
                }
            }
        %>
    </body>
</html>

通过 WebService 请求

如果作为 WebService 项目,则无法简单的通过 img src="kaptcha.jpg" 来获取验证码图片,而是需要前端主动发起一次请求,而后台则是需要将图片写入 HttpServletResponse

后台配置

使用该方法,则不再需要配置 web.xml 。
我的项目是 Spring + cxf 的 WebService 项目,以此为例。首先 Spring 项目,需要配置 KaptchaProducer bean ,其次需要为 WebService 项目接口。

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:jaxrs="http://cxf.apache.org/jaxrs"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://cxf.apache.org/jaxrs http://cxf.apache.org/schemas/jaxrs.xsd">

    <jaxrs:server id="captcha" address="/captcha">
        <jaxrs:serviceBeans>
            <ref bean="captchaService"/>
        </jaxrs:serviceBeans>
    </jaxrs:server>

    <bean id="captchaProducer" class="com.google.code.kaptcha.impl.DefaultKaptcha">
        <property name="config">
            <bean class="com.google.code.kaptcha.util.Config">
                <constructor-arg>
                    <props>
                        <!-- Kaptcha 配置 -->
                        <prop key="kaptcha.border">no</prop>
                        <prop key="kaptcha.image.width">250</prop>
                        <prop key="kaptcha.textproducer.font.size">80</prop>
                        <prop key="kaptcha.image.height">90</prop>
                        <prop key="kaptcha.session.key">code</prop>
                        <prop key="kaptcha.textproducer.char.length">4</prop>
                    </props>
                </constructor-arg>
            </bean>
        </property>
    </bean>
</beans>

然后编写验证码接口实现,生成验证码需要一个对应到会话的唯一的 ID,可以使用 token 或者 uuid 等,在缓存中,该 ID 将作为 Map 的 Key 。

@Path(value = "/")
@WebService
//@Produces(value = MediaType.APPLICATION_JSON)
public interface CaptchaService {
    @GET
    @Path(value = "gen")
    void genCaptcha(@Context HttpServletRequest request,
                    @Context HttpServletResponse response,
                    @QueryParam("token") String token) throws IOException;

}
/////////////////////////////////////////////////////////////////////////////////
@Component(value = "captchaService")
public class CaptchaServiceImpl implements CaptchaService {
    private    @Value("${cache.captchaexpire}") int EXPIRE = 5;

    @Override
    public void genCaptcha(HttpServletRequest request, HttpServletResponse response, String token) throws IOException {
        String capText;
        capText = CaptchaUtil.gen(request, response);
        // 自定义的缓存,保存图片验证码,保存 5 min 有效
        SessionCacheManager.getInstance().add(token, CacheKey.CAPTCHA_PC, capText, EXPIRE * 60 * 1000, false);
    }

    /**
     * 采用了 Google 提供的 Kaptcha 验证码库生成验证码
     *
     * @param request  HttpServletRequest 请求
     * @param response HttpServletResponse 返回,用于输出验证码图片流
     */
    private static String gen(HttpServletRequest request,
                             HttpServletResponse response) throws IOException {
        response.setDateHeader("Expires", 0);
        // Set standard HTTP/1.1 no-cache headers.
        response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
        // Set IE extended HTTP/1.1 no-cache headers (use addHeader).
        response.addHeader("Cache-Control", "post-check=0, pre-check=0");
        // Set standard HTTP/1.0 no-cache header.
        response.setHeader("Pragma", "no-cache");
        // return a jpeg
        response.setContentType("image/jpeg");
        // p3p 跨域
        response.setHeader("P3P", "CP='IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT'");

        // create the text for the image
        String capText = captchaProducer.createText();
        // store the text in the session
        request.getSession().setAttribute(Constants.KAPTCHA_SESSION_KEY, capText);

        // create the image with the text
        BufferedImage bi = captchaProducer.createImage(capText);
        ServletOutputStream out = response.getOutputStream();
        // write the data out
        ImageIO.write(bi, "jpg", out);
        try {
            out.flush();
        } finally {
            out.close();
        }
        return capText;
    }
}

前端请求

前端在获得 token 后,再发起 webservice 请求。改变 img src 后,返回的图片将直接显示。

<script type="application/javascript">
    document.getElementById('captcha').src=URL+"/captcha/gen?token="+encodeURIComponent(token)+"&r="+Math.random();

    function refresh() {
        document.getElementById('captcha')
            .src=URL+"/captcha/gen?token="+encodeURIComponent(token)+"&r="+Math.random();
    }
</script>
<img id="captcha" style="vertical-align: middle;" title="点击更换" src="" alt="未预登录,无验证图片" height="40" width="85" onclick="refresh();">

后台验证

因为在生成验证码时,已经将验证码写入了缓存,此处校验只需要从缓存取出验证码,与前端传入的验证码相比较即可。

Kaptcha 配置

属性 说明 默认值
kaptcha.border 是否有边框,可以自己设置yes,no 默认为true
kaptcha.border.color 边框颜色 默认为Color.BLACK
kaptcha.border.thickness 边框粗细度 默认为1
kaptcha.producer.impl 验证码生成器 默认为DefaultKaptcha
kaptcha.textproducer.impl 验证码文本生成器 默认为DefaultTextCreator
kaptcha.textproducer.char.string 验证码文本字符内容范围 默认为abcde2345678gfynmnpwx
kaptcha.textproducer.char.length 验证码文本字符长度 默认为5
kaptcha.textproducer.font.names 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize)
kaptcha.textproducer.font.size 验证码文本字符大小 默认为40
kaptcha.textproducer.font.color 验证码文本字符颜色 默认为Color.BLACK
kaptcha.textproducer.char.space 验证码文本字符间距 默认为2
kaptcha.noise.impl 验证码噪点生成对象 默认为DefaultNoise
kaptcha.noise.color 验证码噪点颜色 默认为Color.BLACK
kaptcha.obscurificator.impl 验证码样式引擎 默认为WaterRipple
kaptcha.word.impl 验证码文本字符渲染 默认为DefaultWordRenderer
kaptcha.background.impl 验证码背景生成器 默认为DefaultBackground
kaptcha.background.clear.from 验证码背景颜色渐进 默认为Color.LIGHT_GRAY
kaptcha.background.clear.to 验证码背景颜色渐进 默认为Color.WHITE
kaptcha.image.width 验证码图片宽度 默认为200
kaptcha.image.height 验证码图片高度 默认为50

参考资料

  1. Kaptcha 官网,https://code.google.com/archive/p/kaptcha/
  2. java web项目生成验证码的解决方案,作者 夜空中苦逼的程序员, http://blog.csdn.net/chenghui0317/article/details/12526439
  3. java使用kaptcha 验证码组件,作者 代码如疯, http://blog.csdn.net/mdcmy/article/details/7733796
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,284评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,115评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,614评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,671评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,699评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,562评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,309评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,223评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,668评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,859评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,981评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,705评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,310评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,904评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,023评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,146评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,933评论 2 355

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 172,139评论 25 707
  • JCaptcha 简介 CAPTCHA 全称 Completely Automated Public Turing...
    谁在烽烟彼岸阅读 718评论 0 0
  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,247评论 11 349
  • 如果问起你人生中最重要的事情是什么的时候,我相信每个人都会有不同的回答。 年少时候可能你会有各种的梦想,想以后做什...
    红袖飞扬阅读 8,457评论 0 1
  • 暮刚刚来到这个班的时候,青就坐在她的前面。那个时候暮和青并不认识。暮是一个大大咧咧的女生,那时,她经常吐槽青活...
    素忆1998阅读 921评论 0 0