美文网首页
006.Spring整合JCaptcha并使用Redis存储验证

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

作者: airkisser | 来源:发表于2017-08-26 16:23 被阅读0次

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);
    }


}

相关文章

网友评论

      本文标题:006.Spring整合JCaptcha并使用Redis存储验证

      本文链接:https://www.haomeiwen.com/subject/xnpmdxtx.html