图形验证码使用全解析,提升系统安全性的必备技巧
背景
在如今越来越多人使用互联网程序的背景下,很多的项目也为了应对高并发而想出了各种应对方案,其中就包括防刷和并发缓解,不少公司为了验证不是机器人或者绑定手机号,用的是短发发送验证码的方法。这种现在基本上是通用的方案了
但还有种情况,比如说热门的促销或者活动,使得大量的用户在一瞬间购买某项产品,这里除了要考虑经典的扣减库存问题外,还要考虑当并发量达到了项目规定的限制后,需要先缓解一下,让瞬间请求降下来,那么怎么缓解而且不影响用户体验呢?图形验证码就是经典的解决方案,相信大家在使用各种购买类型的程序时,如电商,购票等,肯定遇到过需要滑动验证码的操作。
而在本人在此大麦网项目中,为了应对用户注册而可能会产生的缓存穿透问题,使用了图形验证码的功能,极大了缓解了数据库的压力
下面来介绍下图形验证码的功能
介绍
行为验证码采用嵌入式集成方式,接入方便,安全,高效。抛弃了传统字符型验证码展示-填写字符-比对答案的流程,采用验证码展示-采集用户行为-分析用户行为流程,用户只需要产生指定的行为轨迹,不需要键盘手动输入,极大优化了传统验证码用户体验不佳的问题;同时,快速、准确的返回人机判定结果。目前对外提供两种类型的验证码,其中包含滑动拼图、文字点选。如图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
方法 - 当
CaptchaServiceFactory
的getCache
方法时,会加载CaptchaServiceFactory
类,从而加载static静态块 - 通过java spi机制扫描出
CaptchaCacheService
的实现类,然后添加到cacheService
中 - 这时调用
CaptchaServiceFactory
的getCache
方法时,就会根据缓存类型从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的保存方式
- 点赞
- 收藏
- 关注作者
评论(0)