登录验证码高扩展性设计方案

登录验证码高扩展性建设方案

本文分享了一种登录验证码高扩展性的建设方案,通过工厂模式+策略模式,增强了验证码服务中验证码生成器、验证码存储器、验证码图片生成器的扩展性,实现了服务组件的多样化,降低了维护成本

1、前言

在进行登录时,无论是账号密码登录,还是第三方登录,总是需要输入验证码。

使用验证码可以进行人机判断,提高安全性,过滤恶性攻击。

验证码一般由一串随机字符组成,当然也存在多种生成方式。

验证码的展现方式有很多种,例如图片,语音,手机验证码。

验证码的存储方式有很多种,例如数据库、缓存、JVM本地内存。

验证码的存储又涉及到另一个问题,验证码唯一id如何生成,在分布式项目中,一般可以通过数据库自增序列、缓存自增序列、UUID、雪花算法等方式生成唯一id。

既然生成一个验证码有这么多种方法,那如何设计一种高扩展性的验证码服务架构,使得各个部分的组件可以自由替换呢?这就是本文需要解决的问题。

技术栈:SpringBoot,MySQL,Redis,zookeeper。

设计模式:策略模式。

2、接口规范

根据上述分析,我们需要分别实现验证码的生成、展示、id生成、存储的接口,一般验证码服务需要对外提供生成验证码和校验验证码的api,我们写在一个接口中,四个组件以内部接口类的形式进行开发。

java 复制代码
/**
 * @version 1.0
 * @description 验证码接口
 * @date 2023/9/29 15:59
 */
public interface CheckCodeService {
	// 生成验证码
    public CheckCodeResultDto generate(CheckCodeParamsDto checkCodeParamsDto);

	// 校验验证码
    public boolean verify(String key, String code);

	// 验证码生成器
    public interface CheckCodeGenerator{
        String generate(int length);
    }
	
	// 唯一id生成
    public interface KeyGenerator{
        String generate(String prefix);
    }

	// 验证码存储器
    public interface CheckCodeStore{
    
        void set(String key, String value, Integer expire);

        String get(String key);

        void remove(String key);
    }
    
	// 展示包装器
    public interface DisplayGenerate{
        String display(String code);
    }
}

3、流程规范

定义好接口规范后,我们需要对流程进行规范,正常流程是使用验证码生成器生成一个随机的验证码,使用id生成器生成一个唯一id,使用存储器存储验证码和id,以不同的展示方式包装验证码并返回。

此时我们还没有实际开发各个生成器模块,只是对流程进行规范,让不同的展示方法代码实现我们这个规范,所以这个应该通过抽象类的方式实现,各个生成器模块以注入的方式得到。

java 复制代码
/**
 * @version 1.0
 * @description 验证码接口
 * @date 2023/9/29 15:59
 */
public abstract class AbstractCheckCodeService implements CheckCodeService {

	// 验证码生成器
    protected CheckCodeGenerator checkCodeGenerator;
	// id生成器
    protected KeyGenerator keyGenerator;
	// 验证码存储器
    protected CheckCodeStore checkCodeStore;
	// 展示包装器
    protected DisplayGenerate displayGenerator;

    public abstract void  setCheckCodeGenerator(CheckCodeGenerator checkCodeGenerator);
    public abstract void  setKeyGenerator(KeyGenerator keyGenerator);
    public abstract void  setCheckCodeStore(CheckCodeStore CheckCodeStore);
    public abstract void  setDisplayGenerate(DisplayGenerate displayGenerate);


	// 生成验证码
    public GenerateResult generate(CheckCodeParamsDto checkCodeParamsDto,Integer code_length,String keyPrefix,Integer expire){
        //生成验证码
        String code = checkCodeGenerator.generate(code_length);
        
        //生成唯一id
        String key = keyGenerator.generate(keyPrefix);

        //存储验证码
        checkCodeStore.set(key,code,expire);
        
        //验证码展示方法
        String display = displayGenerator.display(code);
        
        //返回验证码生成结果
        GenerateResult generateResult = new GenerateResult();
        generateResult.setKey(key);
        generateResult.setCode(code);
        generateResult.setDisplay(display);
        return generateResult;
    }

    @Data
    protected class GenerateResult{
        String key;
        String code;
        String display;
    }


    public abstract CheckCodeResultDto generate(CheckCodeParamsDto checkCodeParamsDto);

	// 校验验证码
    public boolean verify(String key, String code){
        if (StringUtils.isBlank(key) || StringUtils.isBlank(code)){
            return false;
        }
        String code_l = checkCodeStore.get(key);
        if (code_l == null){
            return false;
        }
        boolean result = code_l.equalsIgnoreCase(code);
        if(result){
            //删除验证码
            checkCodeStore.remove(key);
        }
        return result;
    }
}

参数类

java 复制代码
public class CheckCodeParamsDto {
	// 验证码展示类型
    private String checkCodeType;
}

4、验证码的实际开发示范

实际实现抽象类的方法需要完成四个组件的注入即可。

java 复制代码
@Service("CheckCodeService")
public class CheckCodeServiceImpl extends AbstractCheckCodeService implements CheckCodeService {

    @Resource(name="NumberLetterCheckCodeGenerator")
    @Override
    public void setCheckCodeGenerator(CheckCodeGenerator checkCodeGenerator) {
        this.checkCodeGenerator = checkCodeGenerator;
    }

    @Resource(name="UUIDKeyGenerator")
    @Override
    public void setKeyGenerator(KeyGenerator keyGenerator) {
        this.keyGenerator = keyGenerator;
    }

    @Resource(name="MemoryCheckCodeStore")
    @Override
    public void setCheckCodeStore(CheckCodeStore checkCodeStore) {
        this.checkCodeStore = checkCodeStore;
    }

    @Resource(name="PicCheckCodeGenerator")
    @Override
    public void setDisplayGenerate(DisplayGenerator displayGenerator) {
        this.displayGenerator = displayGenerator;
    }

    @Override
    public CheckCodeResultDto generate(CheckCodeParamsDto checkCodeParamsDto) {
        GenerateResult generate = generate(checkCodeParamsDto, 4, "checkcode:", 60);
        String key = generate.getKey();
        String code = generate.getCode();
        String display = generate.getDisplay();
        CheckCodeResultDto checkCodeResultDto = new CheckCodeResultDto();
        checkCodeResultDto.setDisplay(display);
        checkCodeResultDto.setKey(key);
        return checkCodeResultDto;
    }
    
}

涉及的实体类

java 复制代码
/**
 * @version 1.0
 * @description 验证码生成结果类
 * @date 2023/9/29 15:48
 */
@Data
public class CheckCodeResultDto {

    // 验证码id
    private String key;

	// 展示方式
    private String display;
}

四个组件的实现示范:

验证码生成器:

java 复制代码
@Component("NumberLetterCheckCodeGenerator")
public class NumberLetterCheckCodeGenerator implements CheckCodeService.CheckCodeGenerator {
    @Override
    public String generate(int length) {
        String str="ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        Random random=new Random();
        StringBuffer sb=new StringBuffer();
        for(int i=0;i<length;i++){
            int number=random.nextInt(36);
            sb.append(str.charAt(number));
        }
        return sb.toString();
    }
}

唯一id生成器(以UUID的方式生成)

java 复制代码
@Component("UUIDKeyGenerator")
public class UUIDKeyGenerator implements CheckCodeService.KeyGenerator {
    @Override
    public String generate(String prefix) {
        String uuid = UUID.randomUUID().toString();
        return prefix + uuid.replaceAll("-", "");
    }
}

验证码存储器(以Redis为例)

java 复制代码
@Component("MemoryCheckCodeStore")
public class MemoryCheckCodeStore implements CheckCodeService.CheckCodeStore {

	@Autowired
	public RedissonClient redissonClient;

    @Override
    public void set(String key, String value, Integer expire) {
    	if (get(key)!=null) log.error("codes key conflict");
        redisTemplate.opsForValue().set(key, value, 300 + new Random().nextInt(100), TimeUnit.SECONDS);
    }

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

    @Override
    public void remove(String key) {
        boolean b = redisTemplate.opsForValue().getOperations().delete(key);
        if (!b) log.info("remove codes key failure");
    }
}

展示生成器(以图片为例)

java 复制代码
@Component("PicCheckCodeGenerate")
public class PicCheckCodeGenerate implements CheckCodeService.DisplayGenerate {
	@Autowired
    private DefaultKaptcha kaptcha;

    @Override
    private String display(String code) {
        // 生成图片验证码
        ByteArrayOutputStream outputStream = null;
        BufferedImage image = kaptcha.createImage(code);

        outputStream = new ByteArrayOutputStream();
        String imgBase64Encoder = null;
        try {
            // 对字节数组Base64编码
            BASE64Encoder base64Encoder = new BASE64Encoder();
            ImageIO.write(image, "png", outputStream);
            imgBase64Encoder = "data:image/png;base64," + EncryptUtil.encodeBase64(outputStream.toByteArray());
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                outputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return imgBase64Encoder;
    }
}

5、总结

本文针对验证码生成过程节点多、实现方案多等特点,提出了一种高扩展性的建设方案,并给出了实际开发示例。

本文提出的验证码生成框架具有高扩展性,可以对任一组件进行创建开发、动态替换。如果结合Nacos和yaml,还可以实现组件热更新。

相关推荐
V+zmm10134几秒前
springcloud分布式架构网上商城 LW +PPT+源码+讲解
java·数据库·后端·vue·idea·springclud
zhangkai__15 分钟前
SpringCloud Feign 报错 Request method ‘POST‘ not supported 的解决办法
python·spring·spring cloud
我非夏日24 分钟前
基于Hadoop平台的电信客服数据的处理与分析③项目开发:搭建基于Hadoop的全分布式集群---任务6:安装并配置Hadoop
大数据·hadoop·分布式
苏生Susheng25 分钟前
【Oracle】Oracle常用语句大全
java·数据库·sql·mysql·oracle·sql语句·数据库语法
拂衣26 分钟前
再谈TileMatrixSet,二维瓦片金字塔结构的标准定义(上)
java·gis
ZJ_.37 分钟前
Node.js 使用 gRPC:从定义到实现
java·开发语言·javascript·分布式·rpc·架构·node.js
基哥的奋斗历程37 分钟前
springboot整合Camunda实现业务
java·spring boot·dubbo
fl_zxf1 小时前
JDK-SPI-服务提供者接口
java·jdk
_Rookie._1 小时前
java 单例模式
java·开发语言·单例模式
hummhumm1 小时前
数据结构第08小节:双端队列
java·数据结构·spring boot·spring·java-ee·maven·intellij-idea