一、问题背景

为了防止垃圾信息发布机器人的自动提交攻击,采用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(2317027), new Color(2203411),
                new Color(2367172) }), 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)生成验证码图片

/**
 * 验证码
 */
@Controller
public 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)
@ResponseBody
public 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的验证码集群实现的更多相关文章

  1. nginx+apache+mysql+php+memcache+squid搭建集群web环境

    服务器的大用户量的承载方案 一.前言 二.编译安装 三. 安装MySQL.memcache 四. 安装Apache.PHP.eAccelerator.php-memcache 五. 安装Squid 六 ...

  2. NoSQL数据库的分布式算法&&memcache集群的实现

    NoSQL数据库的分布式算法  http://blog.nosqlfan.com/html/4139.html 一致性hash算法在memcache集群中的应用   http://alunblog.d ...

  3. memcache 集群

    memcache 是一个分布式的缓存系统,但是本身没有提供集群功能,在大型应用的情况下容易成为瓶颈.但是客户端这个时候可以自由扩展,分两阶段实现.第一阶段:key 要先根据一定的算法映射到一台memc ...

  4. memcache 启动 储存原理 集群

    一. windows下安装启动 首先将memcache的bin目录加入到Path环境变量中,方便后面使用命令: 然后执行 memcached –dinstall 命令安装memcache的服务: 然后 ...

  5. memcache集群

    实现memcache集群   一:memcache本身没有redis锁具备的数据持久化功能,比如RDB和AOF都没有,但是可以通过做集群的方式,让各memcache的数据进行同步,实现数据的一致性,即 ...

  6. nginx+php+memcache实现hash一致性memcache 集群

    我们工作中可能会遇到key-value数据库,如果我们面对的不止一台memcache服务器,而是很多台.那么现在就回出现一个问题: 当我们访问nginx服务器的时候,我们会判断memcache中是否有 ...

  7. 安装memcache集群管理工具

    安装memcache集群管理工具magent 一.安装libevent tar xf libevent--stable.tar.gz cd libevent- ./configure --prefix ...

  8. Memcache 分布式高可用集群介绍

    分布式缓存需考虑如下三点: 1.缓存本身的水平线性扩展的问题. 2.缓存大病罚下的本身性能问题. 3.避免缓存的单点鼓掌问题. 分布式缓存存在的问题: 1.内存本身的管理问题.内存的分配,管理和回收机 ...

  9. 分布式缓存集群方案特性使用场景(Memcache/Redis(Twemproxy/Codis/Redis-cluster))优缺点对比及选型

    分布式缓存集群方案特性使用场景(Memcache/Redis(Twemproxy/Codis/Redis-cluster))优缺点对比及选型   分布式缓存特性: 1) 高性能:当传统数据库面临大规模 ...

随机推荐

  1. (五)maven之外置maven

    eclipse外置maven eclipse内置的maven插件是固定版本,如果要用其他版本的maven,可以使用外置maven. ①    在菜单栏上点击“Windows”à“Preferences ...

  2. SQL与脚本语言

    SQL是人类与数据库沟通的语言https://zhidao.baidu.com/question/413397944.html我个人认为SQL是一种专门对数据库进行操作的特殊的脚本语言.因为SQL语句 ...

  3. Robot Framework(十三) 执行测试用例——创建输出

    3.5创建输出 执行测试时会创建几个输出文件,并且所有这些文件都与测试结果有某种关联.本节讨论创建的输出,如何配置它们的创建位置以及如何微调其内容. 3.5.1不同的输出文件 输出目录 输出文件 日志 ...

  4. 安装linux虚拟机(Ubuntu & KALI)

    VMware workstation 15.0.0 ubuntu-18.10-desktop 首先安装VMware 参考资料很多,不再赘述. 之后参考 https://www.cnblogs.com/ ...

  5. charles连接手机抓包--------最详细的步骤

    首先确保电脑和手机连接到同一个热点上 电脑连接热点以后,首先打开Charles设置Charles的setting port一般都默认8888 Enable transparent HTTP proxy ...

  6. s:iterator的多层迭代

    struts2的s:iterator 可以遍历 数据栈里面的任何数组,集合等等 以下几个简单的demo:s:iterator 标签有3个属性:    value:被迭代的集合    id   :指定集 ...

  7. java在线聊天项目 使用SWT快速制作登录窗口,可视化窗口Design 更换窗口默认皮肤(切换Swing自带的几种皮肤如矩形带圆角)

    SWT成功激活后 new一个JDialog 调整到Design视图 默认的视图模式是BorderLayout,无论你怎么拖拽,只能放到东西南北中的位置上 所以,我们把视图模式调整为AbsoluteLa ...

  8. 使用xib开发界面

    使用xib开发界面 2015-02-02 10:03 编辑: suiling 分类:iOS开发 来源:jymn_chen‘s blog   纯代码写界面有时候会降低开发效率,对于一些通用简单的界面,例 ...

  9. python常用内置函数用法精要

    用一个表格大致总结一下所有的内置函数用法,如下: 函数 功能简要说明 abs(x) 返回数字x的绝对值或复数x的模 all(iterable) 如果对于可迭代对象中所有元素x都等价于True,则返回T ...

  10. Java8特性详解 lambda表达式 Stream【转】

    本文转自http://www.cnblogs.com/aoeiuv/p/5911692.html 1.lambda表达式 Java8最值得学习的特性就是Lambda表达式和Stream API,如果有 ...