登录验证码高扩展性建设方案
本文分享了一种登录验证码高扩展性的建设方案,通过工厂模式+策略模式,增强了验证码服务中验证码生成器、验证码存储器、验证码图片生成器的扩展性,实现了服务组件的多样化,降低了维护成本
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,还可以实现组件热更新。