JCaptcha+Memcache的验证码集群实现
一、问题背景
为了防止垃圾信息发布机器人的自动提交攻击,采用CAPTCHA验证码来保护该模块,提高攻击者的成本。
二、验证码简介
全自动区分计算机和人类的图灵测试(Completely Automated Public Turing test to tell Computers and Humans Apart,简称CAPTCHA)俗称验证码,是一种区分用户是计算机和人的公共全自动程序。在CAPTCHA测试中,作为服务器的计算机会自动生成一个问题由用户来解答。这个问题可以由计算机生成并评判,但是必须只有人类才能解答。由于计算机无法解答CAPTCHA的问题,所以回答出问题的用户就可以被认为是人类。
验证码作为一种辅助安全手段在Web安全中有着特殊的地位,验证码安全和web应用中的众多漏洞相比似乎微不足道,但是千里之堤毁于蚁穴,有些时候如果能绕过验证码,则可以把手动变为自动,对于Web安全检测有很大的帮助。大部分验证码的设计者都不知道为什么要用到验证码,或者对于如何检验验证码的强度没有任何概念。大多数验证码在实现的时候只是把文字印到背景稍微复杂点的图片上就完事了,程序员没有从根本上了解验证码的设计理念。
JCaptcha即为Java版本的CAPTCHA项目,其是一个开源项目,支持生成图形和声音版的验证码,声音版的验证码需要使用到FreeTTS。
其原理是:服务器端首先随机产生一个字符串,生成一个图片,在后台存储一份。在做验证的时候,通过在后台传进去request参数获取到后台存储的值与输入的值进行比对。基于Servlet的使用方式可以参考官网的tutorial( https://jcaptcha.atlassian.net/wiki/display/general/5+minutes+application+integration+tutorial)
JCaptcha的架构图如下所示:

三、问题分析
JCaptcha默认的实现是基于单机模式(MapCaptchaStore存储信息单机HashMap中),为了适应集群环境可以把验证信息存储在session中,但是要求Web服务器配置session stick或者Session复制。为了实现负载均衡且避免session复制带来的性能损失,集群部署方案是完全分布式的,既不是session stick也不进行session复制。进行验证时,由A节点到B节点进行验证,B节点CaptchaStore中store中得不到当前验证码,无法进行验证。
由上可知,如果把验证码统一存储在一个地方,问题将迎刃而解,故考虑自定义CaptchaStore采用memcache来存储,如下图所示:

四、具体实施
1)在pom.xml加入依赖:
<dependency>
<groupId>com.octo.captcha</groupId>
<artifactId>jcaptcha-all</artifactId>
<version>1.0-RC6</version>
</dependency>
|
2)JCaptcha与Spring集成配置
applicationContext.xml:
<bean id="imageCaptchaService" class="com.octo.captcha.service.image.DefaultManageableImageCaptchaService">
<constructor-arg type="com.octo.captcha.service.captchastore.CaptchaStore" index="0">
<ref bean="myCaptchaStore"/>
</constructor-arg>
<!--which captcha Engine you use-->
<constructor-arg type="com.octo.captcha.engine.CaptchaEngine" index="1">
<ref bean="myCaptchaEngine"/>
</constructor-arg>
<constructor-arg index="2">
<value>180</value>
</constructor-arg>
<constructor-arg index="3">
<value>100000</value>
</constructor-arg>
<constructor-arg index="4">
<value>75000</value>
</constructor-arg>
</bean>
<bean id="myCaptchaStore" class="com.xxx.util.MyCaptchaStore"/>
<!--you can define more than one captcha engine here --><bean id="myCaptchaEngine" class="com.xxx.util.MyCaptchaEngine"/>
|
3)定制CaptchaStore
/** * 定制CaptchaStore
* 线上集群环境,前端可能从A服务器取得验证码,而验证是到B服务器
* 默认的hashmap store是保存在单个Jvm内存中的,这样验证就会有问题
*
*/
public class MyCaptchaStore implements CaptchaStore {
//CacheService负责封装memcache访问功能
@Resource
private CacheService cacheService;
@Override
public boolean hasCaptcha(String id) {
CaptchaAndLocale captcha = cacheService.getCaptcha(id);
return captcha == null ? false : true;
}
@Override
public void storeCaptcha(String id, Captcha captcha) throws CaptchaServiceException {
try {
cacheService.setCaptcha(id,new CaptchaAndLocale(captcha));
} catch (Exception e) {
throw new CaptchaServiceException(e);
}
}
@Override
public void storeCaptcha(String id, Captcha captcha, Locale locale) throws CaptchaServiceException {
try {
cacheService.setCaptcha(id,new CaptchaAndLocale(captcha,locale));
} catch (Exception e) {
throw new CaptchaServiceException(e);
}
}
@Override
public boolean removeCaptcha(String id) {
return cacheService.removeCaptcha(id);
}
@Override
public Captcha getCaptcha(String id) throws CaptchaServiceException {
CaptchaAndLocale captchaAndLocale = cacheService.getCaptcha(id);
return captchaAndLocale != null ? (captchaAndLocale.getCaptcha()) : null;
}
@Override
public Locale getLocale(String id) throws CaptchaServiceException {
CaptchaAndLocale captchaAndLocale = cacheService.getCaptcha(id);
return captchaAndLocale != null ? (captchaAndLocale.getLocale()) : null;
}
@Override
public int getSize() {
return 0;
}
@Override
public Collection getKeys() {
return null;
}
@Override
public void empty() {
}
@Override
public void initAndStart() {
}
@Override
public void cleanAndShutdown() {
}
} |
4)定制验证码Engine
/** * 验证码Engine
*
*/
public class MyCaptchaEngine extends ListImageCaptchaEngine {
protected void buildInitialFactories() {
int minWordLength = 4;
int maxWordLength = 4;
int fontSize = 20;
int imageWidth = 100;
int imageHeight = 30;
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.LIGHT_GRAY);
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));
}
} |
5)生成验证码图片
/** * 验证码
*/
@Controllerpublic class CaptchaController {
private static final Logger LOGGER = LoggerFactory.getLogger(CaptchaController.class);
@Resource
private ImageCaptchaService imageCaptchaService;
@RequestMapping(value = "/jcaptcha")
public void ImageCaptcha(HttpServletRequest request , HttpServletResponse response) throws IOException {
String captchaId = UUID.randomUUID().toString();
BufferedImage image = imageCaptchaService.getImageChallengeForID(captchaId, request.getLocale());
response.setHeader("Cache-Control", "no-store");
response.setHeader("Pragma", "no-cache");
response.setDateHeader("Expires", 0);
response.setContentType("image/jpeg");
Cookie cookie = new Cookie(Constants.CAPTCHA_COOKIE_NAME,captchaId);
cookie.setMaxAge(30*60);
response.addCookie(cookie);
ServletOutputStream responseOutputStream = response.getOutputStream();
ImageIO.write(image, "jpg", responseOutputStream);
try {
responseOutputStream.flush();
} finally {
responseOutputStream.close();
}
}
} |
6)验证过程
@RequestMapping(value = "/test",method = RequestMethod.POST)
@ResponseBodypublic JsonResponse test( @RequestParam(value = "content") String content,
@RequestParam(value = "authCode") String authCode,
HttpServletRequest request) {
String captchaId = null;
Cookie[] cookies = request.getCookies();
for (Cookie cookie : cookies) {
if (cookie.getName().equals(Constants.CAPTCHA_COOKIE_NAME)) {
captchaId = cookie.getValue();
break;
}
}
if (StringUtil.isBlank(captchaId)) {
return new JsonResponse(40401,"验证码错误");
}
Boolean flag = false;
try {
flag = imageCaptchaService.validateResponseForID(captchaId, authCode);
} catch (CaptchaServiceException cse) {
}
if (!flag) {
return new JsonResponse(40401,"验证码错误");
}
doSomething(); //业务任务
return new JsonResponse(200,"success");
}五、参考资料
|
JCaptcha+Memcache的验证码集群实现的更多相关文章
- nginx+apache+mysql+php+memcache+squid搭建集群web环境
服务器的大用户量的承载方案 一.前言 二.编译安装 三. 安装MySQL.memcache 四. 安装Apache.PHP.eAccelerator.php-memcache 五. 安装Squid 六 ...
- NoSQL数据库的分布式算法&&memcache集群的实现
NoSQL数据库的分布式算法 http://blog.nosqlfan.com/html/4139.html 一致性hash算法在memcache集群中的应用 http://alunblog.d ...
- memcache 集群
memcache 是一个分布式的缓存系统,但是本身没有提供集群功能,在大型应用的情况下容易成为瓶颈.但是客户端这个时候可以自由扩展,分两阶段实现.第一阶段:key 要先根据一定的算法映射到一台memc ...
- memcache 启动 储存原理 集群
一. windows下安装启动 首先将memcache的bin目录加入到Path环境变量中,方便后面使用命令: 然后执行 memcached –dinstall 命令安装memcache的服务: 然后 ...
- memcache集群
实现memcache集群 一:memcache本身没有redis锁具备的数据持久化功能,比如RDB和AOF都没有,但是可以通过做集群的方式,让各memcache的数据进行同步,实现数据的一致性,即 ...
- nginx+php+memcache实现hash一致性memcache 集群
我们工作中可能会遇到key-value数据库,如果我们面对的不止一台memcache服务器,而是很多台.那么现在就回出现一个问题: 当我们访问nginx服务器的时候,我们会判断memcache中是否有 ...
- 安装memcache集群管理工具
安装memcache集群管理工具magent 一.安装libevent tar xf libevent--stable.tar.gz cd libevent- ./configure --prefix ...
- Memcache 分布式高可用集群介绍
分布式缓存需考虑如下三点: 1.缓存本身的水平线性扩展的问题. 2.缓存大病罚下的本身性能问题. 3.避免缓存的单点鼓掌问题. 分布式缓存存在的问题: 1.内存本身的管理问题.内存的分配,管理和回收机 ...
- 分布式缓存集群方案特性使用场景(Memcache/Redis(Twemproxy/Codis/Redis-cluster))优缺点对比及选型
分布式缓存集群方案特性使用场景(Memcache/Redis(Twemproxy/Codis/Redis-cluster))优缺点对比及选型 分布式缓存特性: 1) 高性能:当传统数据库面临大规模 ...
随机推荐
- CPP-基础:非静态成员函数后面加const,以及mutable修饰成员变量
非静态成员函数后面加const(加到非成员函数或静态成员后面会产生编译错误),表示成员函数隐含传入的this指针为const指针,决定了在该成员函数中,任意修改它所在的类的成员的操作都是不允许的(因为 ...
- Codeforces Round #275(Div. 2)-C. Diverse Permutation
http://codeforces.com/contest/483/problem/C C. Diverse Permutation time limit per test 1 second memo ...
- 八 个优秀的 jQuery Mobile 教程
jQuery Mobile 是 jQuery 在手机上和平板设备上的版本.jQuery Mobile不仅会给主流移动平台带来jQuery核心库,而且会发布一个完整统一的jQuery移动UI框架.虽然j ...
- Bootstrap CSS概览
HTML5文档类型(<!DOCTYPE html>) Bootstrap前端框架使用了HTML5和CSS属性,为了让这些能正常工作,您需要使用HTML5文档类型(<!DOCTYPE ...
- 121. Best Time to Buy and Sell Stock@python
Say you have an array for which the ith element is the price of a given stock on day i. If you were ...
- 洛谷 P2921 在农场万圣节
https://www.luogu.org/problemnew/show/P2921 开始感觉这题30行代码就可以搞定,还是太菜啦,还是乖乖地写了tarjan. 对图进行缩点,那么这个强联通分量中的 ...
- BZOJ 4016 最短路径树问题 最短路径树构造+点分治
题目: BZOJ4016最短路径树问题 分析: 大家都说这是一道强行拼出来的题,属于是两种算法的模板题. 我们用dijkstra算法算出1为源点的最短路数组,然后遍历一下建出最短路树. 之后就是裸的点 ...
- 单机简单搭建一个kafka集群(没有进行内核参数和JVM的调优)
1.JDK安装 在我的部署单节点kafka的博客里有相关的方法.(https://www.cnblogs.com/ToBeExpert/p/9789486.html )zookeeper和kafka的 ...
- Tcp 三次握手 四次分手
看了 余晟以为的 “tcp没那么难吧”,算是对三次握手,四次分手有了一点点理解,记录下来以方便自己以后的查看. 原文链接:https://mp.weixin.qq.com/s?__biz=MzA3MD ...
- centos7下添加开机启动
在/etc/systemd/system下创建weblogic .Service touch weblogic.Service 添加启动权限 chmod +x weblogic.Service 编辑w ...