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

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

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

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,还可以实现组件热更新。

相关推荐
小荷才露尖尖角,早有蜻蜓立上头31 分钟前
过滤器的4种创建方式
java
大圣编蚕41 分钟前
Java ByteArrayInputStream 详解:从入门到实战
java·开发语言·算法
程序员阿明1 小时前
idea把插件啥的不默认放在C盘
java·ide·intellij-idea
linweidong2 小时前
中科闻歌Java面试题及参考答案
java·python·大模型·提示词·ai agent·mcp·rag原理
OpenAnolis小助手2 小时前
一句话看透 JVM,SysOM 诊断 Skill 新增 Java 应用诊断能力
java·开发语言·jvm·阿里云·操作系统控制台·sysom
ForteScarlet2 小时前
Kotlin 2.4.20 现已发布,新特性多不多?
android·java·开发语言·开源·kotlin·jetbrains
萧瑟余晖2 小时前
Java深入解析篇六十二之安全机制详解
java·开发语言
小刘在重生~3 小时前
Java 常用类|包装类 装箱拆箱、Integer 缓存面试题
java·python·缓存
Orange_sparkle3 小时前
Pi Agent vs LangGraph:从人机交互、企业 RAG 到持久化工作流的完整选型指南
java·网络·人机交互
Wang's Blog4 小时前
Java框架快速入门: Spring Security+OAuth2之过滤器与过滤器链
java·开发语言·spring