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

发布于:2024-07-02 ⋅ 阅读:(16) ⋅ 点赞:(0)

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

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

1、前言

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

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

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

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

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

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

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

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

设计模式:策略模式。

2、接口规范

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


/**
 * @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,以不同的展示方式包装验证码并返回。

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


/**
 * @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;
    }
}


参数类


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

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

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


@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;
    }
    
}

涉及的实体类


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

    // 验证码id
    private String key;

	// 展示方式
    private String display;
}

四个组件的实现示范:

验证码生成器:


@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的方式生成)


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

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


@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");
    }
}

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


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