jcaptcha实战

18.4.14

1.1、问题(1):咨询页面没有登录的人也可以发邮件,使得恶意攻击可以随意发几千几万封邮件,所以不得不加了验证码。
解决:网上随便找了几篇文章看了看,对验证码的细节操作还是挺多的,按照其中一篇结合spring配置的很容易就成功了,然后就用它了。
1.2、问题(2):线上服务器有两台,才有负载均衡将请求打到不同的机器,使得A机器产生的验证码,有可能在B机器验证,而导致输入正确验证码却显示错误。因此需要在AB两机器中共享用于判断输入验证码判断的captchaStore存在AB共享的Redis中作为value。接下来就是找合适的key,一个浏览器对AB机器会生成不同的sessionid,AB机器正常可以做session共享的,但是会影响性能而被网络人员拒绝(哎,项目也真是烂),所以只好自己生成UUID,先传给前台隐藏标签,再传给两个设备。此时AB两台设备使用同样的key便可以找到共享的captchaStore。
1.3、问题(3):以上实现方式有个问题,用户反复刷新咨询页面,就会不断生成新的UUID,刷新过多会使得reids里面同一时间存储过多验证码对象。当然这些对象在redis中的存活时间可以设置,验证码一般为2分钟。

2、在maven的仓库中只看到1.0版本的jcaptcha

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

3、前台代码如下:
<label>验证码:</label>
<input name="code" type="text" id="code" vld="{required:true}" class="input" style="width:160px;height:30px;margin-left:28px;"/>
<img src="/XXXX/getcode?uuid=$!{uuid}" class="img" onclick="this.src='/XXXX/getcode?id='+new Date()*1+'&uuid=$!{uuid}'" width="160" height="30"/>

4、controller里面的
public void getCode(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse) {
byte[] captchaChallengeAsJpeg = null;
// 输出jpg的字节流
ByteArrayOutputStream jpegOutputStream = new ByteArrayOutputStream();
try {
// String captchaId = httpServletRequest.getSession().getId();
String uuid = httpServletRequest.getParameter("uuid");
BufferedImage challenge = (BufferedImage) captchaService
.getChallengeForID(uuid,
httpServletRequest.getLocale());
// a jpeg encoder
JPEGImageEncoder jpegEncoder = JPEGCodec
.createJPEGEncoder(jpegOutputStream);
jpegEncoder.encode(challenge);
captchaChallengeAsJpeg = jpegOutputStream.toByteArray();
// flush it in the response
httpServletResponse.setHeader("Cache-Control", "no-store");
httpServletResponse.setHeader("Pragma", "no-cache");
httpServletResponse.setDateHeader("Expires", 0);
httpServletResponse.setContentType("image/jpeg");
ServletOutputStream responseOutputStream = httpServletResponse
.getOutputStream();
responseOutputStream.write(captchaChallengeAsJpeg);
responseOutputStream.flush();
responseOutputStream.close();
} catch (Exception e) {
try {
httpServletResponse.sendError(HttpServletResponse.SC_NOT_FOUND);
} catch (IOException e1) {
e1.printStackTrace();
}
return;
}
}
关键代码就一行:BufferedImage challenge = (BufferedImage) captchaService.getChallengeForID(uuid,httpServletRequest.getLocale());
传入的captchaId是uuid。captchaService需要在Spring中配置Bean。

5、spring中配置jcaptcha的Bean
<context:component-scan base-package="com.*****web.controller.* "/>

<bean id="captchaService"
class="com.octo.captcha.service.multitype.GenericManageableCaptchaService">
<description>验证码服务</description>
<constructor-arg index="0">
<ref bean="sessionCaptchaStoreImpl" />
</constructor-arg>
<constructor-arg index="1">
<ref bean="portalCaptchaEngine" />
</constructor-arg>
<constructor-arg index="2">
<value>300</value>
</constructor-arg>
<constructor-arg index="3">
<value>20000</value>
</constructor-arg>
<constructor-arg index="4">
<value>20000</value>
</constructor-arg>
</bean>
<bean id="portalCaptchaEngine" class="com.****.web.util.PortalCaptchaEngine"></bean>
<bean id="sessionCaptchaStoreImpl" class="com.****.jcaptcha.SessionCaptchaStoreImpl" ></bean>

6、验证码配置
public class PortalCaptchaEngine extends ListImageCaptchaEngine {
@Override
protected void buildInitialFactories() {
int minWordLength = 4;
int maxWordLength = 5;
int fontSize = 50;
int imageWidth = 152;
int imageHeight = 100;
WordGenerator wordGenerator = new RandomWordGenerator(
"0123456789abcdefghijklmnopqrstuvwxyz");
TextPaster randomPaster = new DecoratedRandomTextPaster(minWordLength,
maxWordLength, new RandomListColorGenerator(new Color[] {
new Color(23, 170, 27), new Color(220, 34, 11),
new Color(23, 67, 172) }), new TextDecorator[] {});
BackgroundGenerator background = new UniColorBackgroundGenerator(
imageWidth, imageHeight, Color.white);
FontGenerator font = new RandomFontGenerator(fontSize, fontSize,
new Font[] { new Font("nyala", Font.BOLD, fontSize),
new Font("Bell MT", Font.PLAIN, fontSize),
new Font("Credit valley", Font.BOLD, fontSize) });
ImageDeformation postDef = new ImageDeformationByFilters(
new ImageFilter[] {});
ImageDeformation backDef = new ImageDeformationByFilters(
new ImageFilter[] {});
ImageDeformation textDef = new ImageDeformationByFilters(
new ImageFilter[] {});
WordToImage word2image = new DeformedComposedWordToImage(font,
background, randomPaster, backDef, textDef, postDef);
addFactory(new GimpyFactory(wordGenerator, word2image));
}
}

7.扩展captchaStore,使用redis进行存取。
public class SessionCaptchaStoreImpl implements CaptchaStore {
private Set<String> keySet;//用于必须实现的方法getSize()/getKeys
private static final String REDIS_XXXX_XXXXX_KEY = "项目名功能页面名_";//区别其他验证码
private static final Integer REDIS_XXXX_KEY_EXPIRED = 5 * 60; //time unit: second
@Resource
private RedisUtil redisUtil;
public SessionCaptchaStoreImpl() {
this.keySet = new HashSet<String>();
}
@Override
public boolean hasCaptcha(String s) {
Object o = redisUtil.select(REDIS_XXXX_XXXXX_KEY+s);
if (o == null || o.equals("")) {
return false;
}else {
return true;
}
}
@Override
public void storeCaptcha(String s, Captcha captcha) throws CaptchaServiceException {
captcha.getChallenge();
keySet.add(REDIS_XXXX_XXXXX_KEY + s);
if (hasCaptcha(s)) {
redisUtil.delete(REDIS_XXXX_XXXXX_KEY + s);
}
redisUtil.putByTime(REDIS_XXXX_XXXXX_KEY+s,new CaptchaAndLocale(captcha),REDIS_XXXX_KEY_EXPIRED);
}
@Override
public void storeCaptcha(String s, Captcha captcha, Locale locale) throws CaptchaServiceException {
captcha.getChallenge();
keySet.add(REDIS_XXXX_XXXXX_KEY + s);
if (hasCaptcha(s)) {
redisUtil.delete(REDIS_XXXX_XXXXX_KEY + s);
}
redisUtil.putByTime(REDIS_XXXX_XXXXX_KEY+s,new CaptchaAndLocale(captcha,locale),REDIS_XXXX_KEY_EXPIRED);
}
@Override
public boolean removeCaptcha(String s) {
if (redisUtil.select(REDIS_XXXX_XXXXX_KEY + s) != null) {
keySet.remove(REDIS_XXXX_XXXXX_KEY + s);
redisUtil.delete(REDIS_XXXX_XXXXX_KEY + s);
return true;
}
return false;
}
@Override
public Captcha getCaptcha(String s) throws CaptchaServiceException {
Object captchaAndLocale = redisUtil.select(REDIS_XXXX_XXXXX_KEY+s);
if (captchaAndLocale == null || captchaAndLocale.equals("")) {
return null;
}else {
return ((CaptchaAndLocale)captchaAndLocale).getCaptcha();
}
}
@Override
public Locale getLocale(String s) throws CaptchaServiceException {
Object captchaAndLocale = redisUtil.select(REDIS_XXXX_XXXXX_KEY+s);
if (captchaAndLocale == null || captchaAndLocale.equals("")) {
return null;
}else {
return ((CaptchaAndLocale)captchaAndLocale).getLocale();
}
}
@Override
public int getSize() {
return keySet.size();
}
@Override
public Collection getKeys() {
return keySet;
}
@Override
public void empty() {
for (Iterator<String> iterator = keySet.iterator(); iterator.hasNext();) {
String key = iterator.next();
keySet.remove(key);
redisUtil.delete(key);
}
}
@Override
public void initAndStart() {
}
@Override
public void cleanAndShutdown() {
}
}

8、验证逻辑
@RequestMapping(value = "/XXXX/getJCaptchaState")
@ResponseBody
public Message getJCaptchaState(HttpServletRequest request,String code){
LOGGER.info("输入的验证码:"+code);
Message msg = Message.success();

    Boolean isCorrect = Boolean.FALSE;
    //String captchaId = request.getSession().getId();
    String uuid = request.getParameter("uuid");
    try {
        isCorrect = captchaService.validateResponseForID(uuid,code);
        if (isCorrect) {
            msg.setData(isCorrect);
            msg.setCode("success");
        } else {
            msg.setData(isCorrect);
            msg.setCode("error");
        }
    } catch (Exception e) {
        LOGGER.error(e.getMessage());
    }
    return msg;
}

9、前端js提交
var captcha = getJCaptchaState($("#uuid").val());
if(!captcha){
layer.alert("验证码错误");
$("img.img").trigger("click");
return;
}
function getJCaptchaState(uuid) {
var flag = false;
var code = {"code":$("#code").val(),"uuid":uuid};
$.ajax({
type : "post",
url : "/XXXX/getJCaptchaState",
data : code,
async : false,
success : function(result){
if(result && result.code=="success"&&result.data&&result.data){
flag = true;
}
},
error:function () {
alert("请求出错,请稍后重试");
return false;
}
});
return flag;
}

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

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,559评论 18 139
  • RedisRedis命令参考网址:http://doc.redisfans.com/ Redis 简介Redis ...
    野柳阅读 1,113评论 0 9
  • 1.HashMap是一个数组+链表/红黑树的结构,数组的下标在HashMap中称为Bucket值,每个数组项对应的...
    谁在烽烟彼岸阅读 1,005评论 2 2
  • 当知道某一个人的某些事迹时,挥之不去的好奇心总在怂恿你,去看看吧,了解一下他吧。赵璞玲的三月初三,佛爷和红二爷的...
    荍玊阅读 510评论 0 0
  • 你是我不小心溅落画卷中的一滩浓墨 作者:千年不死等花开 前世 你是我不小心溅落画卷中的一滩浓墨 氤氲弥散,潜移墨化...
    千年不死等花开阅读 450评论 77 33