006.Spring整合JCaptcha并使用Redis存储验证码

1.配置Redis

配置参考:005.整合Spring session和Redis

2.添加Jcaptcha依赖

        <dependency>
            <groupId>com.octo.captcha</groupId>
            <artifactId>jcaptcha</artifactId>
            <version>1.0</version>
        </dependency>

3.spring-captcha.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <context:property-placeholder location="classpath:captcha.properties" order="3" ignore-unresolvable="true"/>

    <!--验证码服务-->
    <bean id="captchaService" class="com.airkisser.familyFinance.utils.jcaptcha.AirManageableCaptchaService">
        <constructor-arg name="captchaEngine" ref="captchaEngine"/>
        <constructor-arg name="captchaStore" ref="captchaStore"/>
        <!--Captcha session过期时间,单位秒-->
        <constructor-arg name="minGuarantedStorageDelayInSeconds"
                         value="${captcha.service.minGuarantedStorageDelayInSeconds}"/>
        <!--最大并发数-->
        <constructor-arg name="maxCaptchaStoreSize" value="${captcha.service.maxCaptchaStoreSize}"/>
        <constructor-arg name="captchaStoreLoadBeforeGarbageCollection"
                         value="${captcha.service.captchaStoreLoadBeforeGarbageCollection}"/>
    </bean>

    <!--验证码存储管理容器:使用redis-->
    <bean id="captchaStore" class="com.airkisser.familyFinance.utils.jcaptcha.AirRedisCaptchaStore">
        <property name="redisTemplate" ref="sessionRedisTemplate"/>
    </bean>

    <!--图片引擎-->
    <bean id="captchaEngine" class="com.octo.captcha.engine.GenericCaptchaEngine">
        <constructor-arg name="factories">
            <list>
                <ref bean="gimpyFactory"/>
            </list>
        </constructor-arg>
    </bean>

    <!--验证码工厂-->
    <bean id="gimpyFactory" class="com.octo.captcha.image.gimpy.GimpyFactory">
        <constructor-arg name="generator" ref="wordGenerator"/>
        <constructor-arg name="word2image" ref="wordToImage"/>
    </bean>

    <!--文字产生器-->
    <bean id="wordGenerator" class="com.octo.captcha.component.word.wordgenerator.RandomWordGenerator">
        <constructor-arg name="acceptedChars" value="${captcha.word.acceptedChars}"/>
    </bean>

    <bean id="wordToImage" class="com.octo.captcha.component.image.wordtoimage.ComposedWordToImage">
        <constructor-arg name="fontGenerator" ref="fontGenerator"/>
        <constructor-arg name="background" ref="backgroundGenerator"/>
        <constructor-arg name="textPaster" ref="textPaster"/>
    </bean>

    <!--字体生成器-->
    <bean id="fontGenerator" class="com.octo.captcha.component.image.fontgenerator.RandomFontGenerator">
        <constructor-arg name="minFontSize" value="${captcha.font.minSize}"/>
        <constructor-arg name="maxFontSize" value="${captcha.font.maxSize}"/>
        <constructor-arg name="fontsList">
            <list>
                <ref bean="fontVerdana"/>
                <ref bean="fontTahoma"/>
                <ref bean="fontLucida"/>
                <ref bean="fontComic"/>
                <ref bean="fontArial"/>
            </list>
        </constructor-arg>
    </bean>

    <!--颜色生成器-->
    <bean id="colorGenerator" class="com.octo.captcha.component.image.color.RandomRangeColorGenerator">
        <constructor-arg name="redComponentRange">
            <list>
                <value>${captcha.color.range.red.min}</value>
                <value>${captcha.color.range.red.max}</value>
            </list>
        </constructor-arg>
        <constructor-arg name="greenComponentRange">
            <list>
                <value>${captcha.color.range.green.min}</value>
                <value>${captcha.color.range.green.max}</value>
            </list>
        </constructor-arg>
        <constructor-arg name="blueComponentRange">
            <list>
                <value>${captcha.color.range.blue.min}</value>
                <value>${captcha.color.range.blue.max}</value>
            </list>
        </constructor-arg>
    </bean>

    <!--line colors-->
    <bean id="lineColorGenerator" class="com.octo.captcha.component.image.color.RandomRangeColorGenerator">
        <constructor-arg name="redComponentRange">
            <list>
                <value>${captcha.line.color.range.red.min}</value>
                <value>${captcha.line.color.range.red.max}</value>
            </list>
        </constructor-arg>
        <constructor-arg name="greenComponentRange">
            <list>
                <value>${captcha.line.color.range.green.min}</value>
                <value>${captcha.line.color.range.green.max}</value>
            </list>
        </constructor-arg>
        <constructor-arg name="blueComponentRange">
            <list>
                <value>${captcha.line.color.range.blue.min}</value>
                <value>${captcha.line.color.range.blue.max}</value>
            </list>
        </constructor-arg>
    </bean>

    <!--背景生成器-->
    <bean id="backgroundGenerator"
          class="com.octo.captcha.component.image.backgroundgenerator.UniColorBackgroundGenerator">
        <constructor-arg name="width" value="${captcha.bg.width}"/>
        <constructor-arg name="height" value="${captcha.bg.height}"/>
        <constructor-arg name="color" value="${captcha.bg.color}"/>
    </bean>

    <!--字符贴片-->
    <bean id="textPaster" class="com.octo.captcha.component.image.textpaster.DecoratedRandomTextPaster">
        <constructor-arg name="minAcceptedWordLength" value="${captcha.word.minLength}"/>
        <constructor-arg name="maxAcceptedWordLength" value="${captcha.word.maxLength}"/>
        <constructor-arg name="colorGenerator" ref="colorGenerator"/>
        <constructor-arg name="decorators">
            <list>
                <ref bean="lineTextDecorator"/>
                <ref bean="baffleTextDecorator"/>
            </list>
        </constructor-arg>
    </bean>

    <!--干扰线-->
    <bean id="lineTextDecorator" class="com.octo.captcha.component.image.textpaster.textdecorator.LineTextDecorator">
        <constructor-arg name="numberOfLinesPerGlyph" value="${captcha.line.numberOfLinesPerGlyph}"/>
        <constructor-arg name="linesColorGenerator" ref="lineColorGenerator"/>
    </bean>

    <bean id="baffleTextDecorator"
          class="com.octo.captcha.component.image.textpaster.textdecorator.BaffleTextDecorator">
        <constructor-arg name="numberOfHolesPerGlyph" value="${captcha.baffle.numberOfHolesPerGlyph}"/>
        <constructor-arg name="holesColorGenerator" ref="lineColorGenerator"/>
    </bean>

    <!--Fonts-->
    <bean id="fontArial" class="java.awt.Font">
        <constructor-arg index="0" value="Arial"/>
        <constructor-arg index="1" value="${captcha.font.style}"/>
        <constructor-arg index="2" value="${captcha.font.size}"/>
    </bean>
    <bean id="fontTahoma" class="java.awt.Font">
        <constructor-arg index="0" value="Tahoma"/>
        <constructor-arg index="1" value="${captcha.font.style}"/>
        <constructor-arg index="2" value="${captcha.font.size}"/>
    </bean>
    <bean id="fontVerdana" class="java.awt.Font">
        <constructor-arg index="0" value="Verdana"/>
        <constructor-arg index="1" value="${captcha.font.style}"/>
        <constructor-arg index="2" value="${captcha.font.size}"/>
    </bean>
    <bean id="fontComic" class="java.awt.Font">
        <constructor-arg index="0" value="Comic sans MS"/>
        <constructor-arg index="1" value="${captcha.font.style}"/>
        <constructor-arg index="2" value="${captcha.font.size}"/>
    </bean>
    <bean id="fontLucida" class="java.awt.Font">
        <constructor-arg index="0" value="Lucida console"/>
        <constructor-arg index="1" value="${captcha.font.style}"/>
        <constructor-arg index="2" value="${captcha.font.size}"/>
    </bean>

</beans>

captcha.properties

# CaptchaService设置
# --Captcha session过期时间,单位秒
captcha.service.minGuarantedStorageDelayInSeconds=60
# --最大并发数
captcha.service.maxCaptchaStoreSize=100
captcha.service.captchaStoreLoadBeforeGarbageCollection=100

# 字体设置,字体style、size,size最大最小值
captcha.font.style=1
captcha.font.size=24
captcha.font.minSize=24
captcha.font.maxSize=28

# 验证码文本,生成文本选择的字符,最小和最大文本长度
captcha.word.acceptedChars=1234567890
captcha.word.minLength=4
captcha.word.maxLength=4

# 背景生成,背景图片宽度和高度以及背景颜色
captcha.bg.width=150
captcha.bg.height=42
captcha.bg.color=WHITE

# 干扰线,每个字符干扰线数量
captcha.line.numberOfLinesPerGlyph=1
captcha.baffle.numberOfHolesPerGlyph=1

# 颜色rgb Range
captcha.color.range.red.min=0
captcha.color.range.red.max=150
captcha.color.range.green.min=0
captcha.color.range.green.max=150
captcha.color.range.blue.min=0
captcha.color.range.blue.max=150

captcha.line.color.range.red.min=180
captcha.line.color.range.red.max=255
captcha.line.color.range.green.min=180
captcha.line.color.range.green.max=255
captcha.line.color.range.blue.min=180
captcha.line.color.range.blue.max=255

4.验证码存储器Redis实现

package com.airkisser.familyFinance.utils.jcaptcha;

import com.octo.captcha.Captcha;
import com.octo.captcha.service.CaptchaServiceException;
import com.octo.captcha.service.captchastore.CaptchaAndLocale;
import com.octo.captcha.service.captchastore.CaptchaStore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;

import java.io.Serializable;
import java.util.Collection;
import java.util.Locale;

/**
 * 基于Redis管理的CaptchaStore
 *
 * @author luojun at 2017/8/12,QQ: 1146874762
 */
public class AirRedisCaptchaStore implements CaptchaStore {
    private static final Logger CAPTCHA_LOG = LoggerFactory.getLogger(AirRedisCaptchaStore.class);

    private static final String CAPTCHA_SESSION_KEY = "com:airkisser:captcha";

    private RedisTemplate<String, Serializable> redisTemplate;
    private HashOperations<String, String, Serializable> hashOperations;

    @Override
    public boolean hasCaptcha(String sid) {
        return this.hashOperations.hasKey(CAPTCHA_SESSION_KEY, sid);
    }

    @Override
    public void storeCaptcha(String sid, Captcha captcha) throws CaptchaServiceException {
        CAPTCHA_LOG.debug("Store captcha, Sid: {}.",sid);
        this.hashOperations.put(CAPTCHA_SESSION_KEY, sid, captcha);
    }

    @Override
    public void storeCaptcha(String sid, Captcha captcha, Locale locale) throws CaptchaServiceException {
        CAPTCHA_LOG.debug("Store captcha, Sid: {}.",sid);
        this.hashOperations.put(CAPTCHA_SESSION_KEY, sid, new CaptchaAndLocale(captcha, locale));
    }

    @Override
    public boolean removeCaptcha(String sid) {
        if (this.hasCaptcha(sid)) {
            CAPTCHA_LOG.debug("Remove captcha, Sid: {}.",sid);
            this.hashOperations.delete(CAPTCHA_SESSION_KEY, sid);
            return true;
        } else {
            return false;
        }
    }

    @Override
    public Captcha getCaptcha(String sid) throws CaptchaServiceException {
        Object val = this.hashOperations.get(CAPTCHA_SESSION_KEY, sid);
        if (val == null) return null;
        if (val instanceof Captcha) return (Captcha) val;
        if (val instanceof CaptchaAndLocale) return ((CaptchaAndLocale) val).getCaptcha();
        return null;
    }

    @Override
    public Locale getLocale(String sid) throws CaptchaServiceException {
        Object captchaAndLocale = this.getCaptcha(sid);
        if (captchaAndLocale != null && captchaAndLocale instanceof CaptchaAndLocale) {
            return ((CaptchaAndLocale) captchaAndLocale).getLocale();
        }
        return null;
    }

    @Override
    public int getSize() {
        CAPTCHA_LOG.debug("Get captcha size.");
        return Math.toIntExact(this.hashOperations.size(CAPTCHA_SESSION_KEY));
    }

    @Override
    public Collection getKeys() {
        CAPTCHA_LOG.debug("Get captcha keys.");
        return this.hashOperations.keys(CAPTCHA_SESSION_KEY);
    }

    @Override
    @SuppressWarnings("unchecked")
    public void empty() {
        Collection<String> keys = this.getKeys();
        if (!keys.isEmpty()) {
            CAPTCHA_LOG.debug("Empty captcha.");
            this.hashOperations.delete(CAPTCHA_SESSION_KEY, keys.toArray());
        }
    }

    @Override
    public void initAndStart() {
        CAPTCHA_LOG.debug("InitAndStart captcha.");
        this.empty();
    }

    @Override
    public void cleanAndShutdown() {
        CAPTCHA_LOG.debug("CleanAndShutdown captcha.");
        this.empty();
    }

    @SuppressWarnings("unchecked")
    public void setRedisTemplate(RedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
        this.hashOperations = this.redisTemplate.opsForHash();
    }

}

5.每次验证验证码以后,无论成功与否,验证码服务默认会删除生成的验证信息,此处重写验证逻辑,修改为仅验证通过以后才删除验证信息

package com.airkisser.familyFinance.utils.jcaptcha;

import com.octo.captcha.Captcha;
import com.octo.captcha.engine.CaptchaEngine;
import com.octo.captcha.service.CaptchaServiceException;
import com.octo.captcha.service.captchastore.CaptchaStore;
import com.octo.captcha.service.multitype.GenericManageableCaptchaService;

import java.util.Locale;

/**
 * GenericManageableCaptchaService扩展
 *
 * @author luojun at 2017/8/12,QQ: 1146874762
 */
public class AirManageableCaptchaService extends GenericManageableCaptchaService {

    public AirManageableCaptchaService(CaptchaStore captchaStore, CaptchaEngine captchaEngine, int minGuarantedStorageDelayInSeconds, int maxCaptchaStoreSize, int captchaStoreLoadBeforeGarbageCollection) {
        super(captchaStore, captchaEngine, minGuarantedStorageDelayInSeconds, maxCaptchaStoreSize, captchaStoreLoadBeforeGarbageCollection);
    }

    /**
     * 重写validateResponseForID方法,默认每次经过该操作都会删除对应的Captcha
     *
     * @param ID       SessionID
     * @param response 提交的验证码参数值
     */
    public Boolean validateResponseForID(String ID, Object response) throws CaptchaServiceException {
        if (!this.store.hasCaptcha(ID)) {
            throw new CaptchaServiceException("Invalid ID, could not validate unexisting or already validated captcha");
        }
        Boolean valid = this.store.getCaptcha(ID).validateResponse(response);
        if (valid) {//如果验证成功,移除Captcha
            this.store.removeCaptcha(ID);
        }
        return valid;
    }

    /**
     * 每次调用都重新生成
     * @param ID
     * @param locale
     * @return
     * @throws CaptchaServiceException
     */
    public Object getChallengeForID(String ID, Locale locale) throws CaptchaServiceException {
        Captcha captcha;
        if(!this.store.hasCaptcha(ID)) {
            this.store.removeCaptcha(ID);
        }
        captcha = this.generateAndStoreCaptcha(locale, ID);
        if(captcha == null) {
            captcha = this.generateAndStoreCaptcha(locale, ID);
        } else if(captcha.hasGetChalengeBeenCalled()) {
            captcha = this.generateAndStoreCaptcha(locale, ID);
        }
        Object challenge = this.getChallengeClone(captcha);
        captcha.disposeChallenge();
        return challenge;
    }

}

6.验证码Controller

package com.airkisser.familyFinance.web;

import com.airkisser.familyFinance.core.result.BooleanResult;
import com.airkisser.familyFinance.core.result.Result;
import com.octo.captcha.service.multitype.GenericManageableCaptchaService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;

/**
 * JCaptcha验证码Controller
 *
 * @author luojun at 2017/8/11,QQ: 1146874762
 */
@Controller
@RequestMapping("/captcha")
public class CaptchaController {
    private static final Logger LOGGER = LoggerFactory.getLogger(CaptchaController.class);

    @Autowired
    private GenericManageableCaptchaService captchaService;

    // 生成验证码
    @RequestMapping(method = RequestMethod.GET)
    public void genCaptcha(HttpServletRequest request, HttpServletResponse response) throws IOException {
        // the output stream to render the captcha image as jpeg into
        ByteArrayOutputStream jpegOutputStream = new ByteArrayOutputStream();
        // get the session id that will identify the generated captcha.
        // the same id must be used to validate the response, the session id is a good candidate!
        String captchaId = request.getSession().getId();
        LOGGER.info("Gen Captcha Session Id:" + captchaId);
        // call the ImageCaptchaService getChallenge method
        BufferedImage challenge = captchaService.getImageChallengeForID(captchaId, request.getLocale());
        // a jpeg encoder
//        JPEGImageEncoder jpegEncoder = JPEGCodec.createJPEGEncoder(jpegOutputStream);
//        jpegEncoder.encode(challenge);
//        byte[] captchaChallengeAsJpeg = jpegOutputStream.toByteArray();
        // flush it in the response
        response.setHeader("Cache-Control", "no-store");
        response.setHeader("Pragma", "no-cache");
        response.setDateHeader("Expires", 0);
        response.setContentType("image/jpeg");
        OutputStream outputStream = response.getOutputStream();
//        outputStream.write(captchaChallengeAsJpeg);
        ImageIO.write(challenge, "jpeg", outputStream);
        outputStream.flush();
        outputStream.close();
    }

    // 验证验证码
    @RequestMapping(value = "/valid")
    @ResponseBody
    public Result validCaptcha(HttpServletRequest request, String captcha) {
        if (StringUtils.isEmpty(captcha)) {
            LOGGER.error("验证码不能为空");
            return BooleanResult.errorResult("验证码不能为空", null);
        }
        String captchaId = request.getSession().getId();
        LOGGER.info("Valid Captcha Session Id:" + captchaId);
        if (!captchaService.validateResponseForID(captchaId, captcha)) {
            LOGGER.error("验证码错误");
            return BooleanResult.errorResult("验证码错误", null);
        }
        return BooleanResult.successResult("验证成功", null);
    }


}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,524评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,869评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,813评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,210评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,085评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,117评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,533评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,219评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,487评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,582评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,362评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,218评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,589评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,899评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,176评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,503评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,707评论 2 335

推荐阅读更多精彩内容