图形验证码使用全解析,提升系统安全性的必备技巧

举报
幼儿园老大* 发表于 2024/11/27 15:40:14 2024/11/27
【摘要】 背景在如今越来越多人使用互联网程序的背景下,很多的项目也为了应对高并发而想出了各种应对方案,其中就包括防刷和并发缓解,不少公司为了验证不是机器人或者绑定手机号,用的是短发发送验证码的方法。这种现在基本上是通用的方案了但还有种情况,比如说热门的促销或者活动,使得大量的用户在一瞬间购买某项产品,这里除了要考虑经典的扣减库存问题外,还要考虑当并发量达到了项目规定的限制后,需要先缓解一下,让瞬间请求...

背景


在如今越来越多人使用互联网程序的背景下,很多的项目也为了应对高并发而想出了各种应对方案,其中就包括防刷和并发缓解,不少公司为了验证不是机器人或者绑定手机号,用的是短发发送验证码的方法。这种现在基本上是通用的方案了


但还有种情况,比如说热门的促销或者活动,使得大量的用户在一瞬间购买某项产品,这里除了要考虑经典的扣减库存问题外,还要考虑当并发量达到了项目规定的限制后,需要先缓解一下,让瞬间请求降下来,那么怎么缓解而且不影响用户体验呢?图形验证码就是经典的解决方案,相信大家在使用各种购买类型的程序时,如电商,购票等,肯定遇到过需要滑动验证码的操作。


而在本人在此大麦网项目中,为了应对用户注册而可能会产生的缓存穿透问题,使用了图形验证码的功能,极大了缓解了数据库的压力


下面来介绍下图形验证码的功能

介绍


行为验证码采用嵌入式集成方式,接入方便,安全,高效。抛弃了传统字符型验证码展示-填写字符-比对答案的流程,采用验证码展示-采集用户行为-分析用户行为流程,用户只需要产生指定的行为轨迹,不需要键盘手动输入,极大优化了传统验证码用户体验不佳的问题;同时,快速、准确的返回人机判定结果。目前对外提供两种类型的验证码,其中包含滑动拼图、文字点选。如图1-1、1-2所示。若希望不影响原UI布局,可采用弹出式交互。


后端基于Java实现,提供纯Java.jar和SpringBoot Starter。前端提供了Android、iOS、Futter、Uni-App、ReactNative、Vue、Angular、Html、Php等多端示例。

为了更加方便的使用aj-captcha验证码功能,本人将此项目集成到了大麦网项目中,作为基础组件来使用,并且将验证码缓存的方式修改成了改为redis来存储,因为在生产环境服务多实例情况下,使用本地缓存肯定是不行的


模块:damai-captcha-framework


<dependency>
    <groupId>com.example</groupId>
    <artifactId>damai-captcha-framework</artifactId>
    <version>${revision}</version>
</dependency>


组件保留了aj-captcha的所有验证功能,并且提供了获取验证码和校验验证码的api,可以根据业务需求来灵活使用


讲解


验证码缓存方式


首先我们看下aj-captcha中是怎么进行加载缓存数据的


Springboot3方式

org.springframework.boot.autoconfigure.AutoConfiguration.imports

com.damai.config.AjCaptchaAutoConfiguration
com.damai.config.CaptchaAutoConfig


Springboot2方式

spring.factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.anji.captcha.config.AjCaptchaAutoConfiguration


使用了springboot的自动装配功能


AjCaptchaAutoConfiguration


@Configuration
@EnableConfigurationProperties(AjCaptchaProperties.class)
@ComponentScan("com.anji.captcha")
@Import({AjCaptchaServiceAutoConfiguration.class, AjCaptchaStorageAutoConfiguration.class})
public class AjCaptchaAutoConfiguration {
}


AjCaptchaStorageAutoConfiguration就是缓存数据的配置


@Configuration
public class AjCaptchaStorageAutoConfiguration {

    @Bean(name = "AjCaptchaCacheService")
    public CaptchaCacheService captchaCacheService(AjCaptchaProperties ajCaptchaProperties){
        //缓存类型redis/local/....
        return CaptchaServiceFactory.getCache(ajCaptchaProperties.getCacheType().name());
    }
}


能够看到是通过ajCaptchaProperties.getCacheType().name()属性从CaptchaServiceFactory工厂中获取


属性通过aj.captcha.cache-type来配置


下面来分析下CaptchaServiceFactory工厂的加载流程


public static CaptchaCacheService getCache(String cacheType) {
    return cacheService.get(cacheType);
}

public volatile static Map<String, CaptchaService> instances = new HashMap();
public volatile static Map<String, CaptchaCacheService> cacheService = new HashMap();

static {
    ServiceLoader<CaptchaCacheService> cacheServices = ServiceLoader.load(CaptchaCacheService.class);
    for (CaptchaCacheService item : cacheServices) {
        cacheService.put(item.type(), item);
    }
    logger.info("supported-captchaCache-service:{}", cacheService.keySet().toString());
    ServiceLoader<CaptchaService> services = ServiceLoader.load(CaptchaService.class);
    for (CaptchaService item : services) {
        instances.put(item.captchaType(), item);
    }
    ;
    logger.info("supported-captchaTypes-service:{}", instances.keySet().toString());
}


cacheService中存在的就是缓存处理类,key为类型, value为CaptchaCacheService的实现类


  • spring启动后,会执行被@Bean修饰的captchaCacheService方法
  • CaptchaServiceFactorygetCache方法时,会加载CaptchaServiceFactory类,从而加载static静态块
  • 通过java spi机制扫描出CaptchaCacheService的实现类,然后添加到cacheService
  • 这时调用CaptchaServiceFactorygetCache方法时,就会根据缓存类型从cacheService中取出


aj-captcha中,默认的缓存策略使用的是本地缓存


private StorageType cacheType = local;


CaptchaCacheServiceMemImpl就是本地缓存策略的实现


/**
 * 对于分布式部署的应用,我们建议应用自己实现CaptchaCacheService,比如用Redis,参考service/spring-boot代码示例。
 * 如果应用是单点的,也没有使用redis,那默认使用内存。
 * 内存缓存只适合单节点部署的应用,否则验证码生产与验证在节点之间信息不同步,导致失败。
 * @Title: 默认使用内存当缓存
 * @author lide1202@hotmail.com
 * @date 2020-05-12
 */
public class CaptchaCacheServiceMemImpl implements CaptchaCacheService {
    @Override
    public void set(String key, String value, long expiresInSeconds) {

        CacheUtil.set(key, value, expiresInSeconds);
    }

    @Override
    public boolean exists(String key) {
        return CacheUtil.exists(key);
    }

    @Override
    public void delete(String key) {
        CacheUtil.delete(key);
    }

    @Override
    public String get(String key) {
        return CacheUtil.get(key);
    }

	@Override
	public Long increment(String key, long val) {
    	Long ret = Long.valueOf(CacheUtil.get(key))+val;
		CacheUtil.set(key,ret+"",0);
		return ret;
	}

	@Override
    public String type() {
        return "local";
    }
}


在生产中高并发的项目肯定都是多实例部署的,所以本地缓存这种方式肯定不行,我们改用redis的方式


使用redis我们要借助springboot提供的redis操作StringRedisTemplate


但要注意,这里缓存策略的实现都是用的java spi加载得到的,而且并没有被spring管理,所以直接通过构造器注入StringRedisTemplate是不行的,需要主动调用方法来进行注入,下面介绍如何改用redis的保存方式


【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。