认证篇:Spring Security 自定义验证码登录(上)之验证码生成

图1-1 验证码生成 概图

概述

总所周知,验证码方式的登录模式十分的普遍,不过 Spring Security并没有提供比较好的原生解决方案,但是我们可以 do it by ourselves!,本文的篇幅相对比较长,因此分上下篇分别来介绍。上篇主要介绍:验证码的生成,下篇对自定义验证码登录的流程进行讲解。

我们比较常见的验证码主要有两种:图形验证码以及短信验证码,相对来说不是特别的复杂。可能会有人有疑惑:为什么简单的验证码生成需要花费一整篇幅来介绍呢?原因当然是:身为菜鸟的我也有一个架构师的梦!验证码的生成会结合模板方法模式一起讲解。

初探模板方法模式

模板方法模式属于一种行为型的设计模式,主要是用来解决复用和扩展两个问题。

模板方法模式在一个方法中定义一个算法骨架,并将某些步骤推迟到某些子类中实现。该模式可以让子类在不改变算法整体结构的情况,重新定义算法中的某些步骤细节。

这里提到了一个算法骨架的概念,算法并非是指数据结构中的“算法”,可以理解为广义上的业务逻辑; 骨架架子其实就是模板;总的来说:算法骨架可以理解为包含广义业务逻辑的模板方法。

实践出真知

绝大部分的设计模式的原理都十分的简单,难得是将原理落实到实践中,解决实际问题。

我们知道模板方法模式主要是用来解决 复用扩展这两个问题,结合到实际情况中来分析;验证码生成有哪些地方需要 复用扩展呢?

让我们来梳理一下验证码登录模式的流程,无论是短信验证码还是图形验证码,大致上都有如下步骤: 生成验证码、存储、发送、校验;既然流程上相同,那么就能做到复用。而 扩展并非是指代码的扩展性,而是指框架上的扩展性,模板方法模式可以让使用者在不修改骨架源码的情况下,定制化扩展功能。

废话不多说,接下来就来瞅瞅模板方法模式在验证码生成模块的落地情况吧!还是老规矩,先上图:

图1-1 验证码关系概览图

验证码的生成主要分3个模块:骨架模块、验证码生命周期模块、具体验证码模块(短信验证码和图形验证码)

  • 骨架模块主要包含 ValidateCodeProcessor接口以及AbstractValidateCodeProcessor抽象类;封装了验证码相关的可复用的业务逻辑。

  • 验证码生命周期模块是指:验证码的生成、存储、发送。

  • 具体验证码模块涉及短信验证码和图形验证码,基于骨架重新定义自己的相关实现。

验证码骨架

无论是图形验证码还是短信验证码,验证码的相关业务逻辑(算法骨架)都是大同小异的;主要是验证码的 创建流程验证流程。因此使用模板方法模式,对可复用的业务逻辑进行抽离,封装成一个骨架。

ValidateCodeProcessor.class

/**
 * 校验码处理器 封装不同验证码的处理逻辑
 *
 * @author 小奇
 * @date 2020/09/26
 */
public interface ValidateCodeProcessor {
   


    /**
     * 创建验证码
     * 1.生成验证码  2.存储  3.发送
     *
     * @param res http请求的request和response封装
     * @throws Exception
     */
    void create(ServletWebRequest res) throws Exception;


    /**
     * 校验验证码
     *
     * @param res
     */
    void validate(ServletWebRequest res);
}

ValidateCodeProcessor接口定义了2个方法:create()方法,用于验证码的生成, validate()方法用于验证码的校验。

AbstractValidateCodeProcessor.class

/**
 * 抽象方法模式——算法骨架
 * 对验证码的一些公有的业务逻辑进行抽离,做到复用
 *
 * @author 小奇
 * @date 2020/09/26
 **/
@Slf4j
public abstract class AbstractValidateCodeProcessor<C extends ValidateCode> implements ValidateCodeProcessor {
   


    /**
     * 收集系统中所有的 {@link ValidateCodeGenerator} 接口的实现。
     */
    @Autowired
    private Map<String, ValidateCodeGenerator> validateCodeGeneratorMap;

    /**
     * 验证码的存储介质
     */
    @Autowired
    private ValidateCodeRepository validateCodeRepository;


    private static final String SMS = "sms", IMAGE = "image";

    @Override
    public void create(ServletWebRequest res) throws Exception {
   
        // 生成
        C validateCode = generate(res);

        // 存储

        save(res, validateCode);

        // 发送 (抽象方法 由具体的子类实现各自的发送逻辑)
        send(res, validateCode);
    }

    @Override
    public void validate(ServletWebRequest res) {
   

        // 根据请求获取验证码的类型,并且从repository存储层中寻找匹配的验证码
        ValidateCodeEnum codeEnum = getValidateCodeType(res);
        Optional<ValidateCode> codeOpt = validateCodeRepository.get(res, codeEnum);
        ValidateCode valCodeInStorage = codeOpt.orElseThrow(() -> new ValidateCodeException("验证码不存在"));

        // 从请求中获取验证码
        String codeInRequest;
        try {
   
            codeInRequest = ServletRequestUtils.getStringParameter(res.getRequest(),
                    codeEnum.getType());
        } catch (ServletRequestBindingException e) {
   
            throw new ValidateCodeException("获取请求验证码的值失败");
        }

        if (StringUtils.isBlank(codeInRequest)) {
   
            throw new ValidateCodeException(codeEnum + "请求验证码的值不能为空");
        }

        // 对短信验证码做一个是否过期的判断
        if (ValidateCodeEnum.SMS.equals(codeEnum) && valCodeInStorage.checkExpired()) {
   
            validateCodeRepository.remove(res, codeEnum);
            throw new ValidateCodeException(codeEnum + "验证码已过期");
        }

        // 验证码校验
        if (!StringUtils.equals(valCodeInStorage.getCode(), codeInRequest)) {
   
            throw new ValidateCodeException(codeEnum + "验证码不匹配");
        }

        log.info("验证码校验成功");
        validateCodeRepository.remove(res, codeEnum);
    }


    /**
     * 生成验证码
     *
     * @param res
     * @return C 验证码泛型
     */
    @SuppressWarnings("unchecked"
  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值