2023-06-18  阅读(3)
原文作者:代码有毒 mrcode 原文地址:https://mrcode.blog.csdn.net/article/details/81502532

图片验证码重构

  • 验证码基本参数可配置
  • 验证码校验拦截的接口可配置
  • 验证码的生成逻辑可配

验证码基本参数配置

三级覆盖:在最上面的会覆盖下级的配置

    ↓ 请求级配置 :配置值在调用接口的时候传递
    ↓ 应用级配置 :配置写在security-demo项目中
    ↓ 默认配置   :配置值写在security-core项目中

图形验证码配置类

    package cn.mrcode.imooc.springsecurity.securitycore.properties;
    
    /**
     * 图形验证码
     * @author zhuqiang
     * @version 1.0.1 2018/8/4 10:03
     * @date 2018/8/4 10:03
     * @since 1.0
     */
    public class ImageCodeProperties {
        private int width = 67;
        private int height = 23;
        private int length = 4;  // 验证码长度
        private int expireIn = 60;  // 过期时间

验证码配置类

    package cn.mrcode.imooc.springsecurity.securitycore.properties;
    
    /**
     * 用来封装验证码相关的配置
     * @author zhuqiang
     * @version 1.0.1 2018/8/4 10:06
     * @date 2018/8/4 10:06
     * @since 1.0
     */
    public class ValidateCodeProperties {
        private ImageCodeProperties image = new ImageCodeProperties();
        // 后面还会新增短信验证码的配置

加入总配置类中

    @ConfigurationProperties(prefix = "imooc.security")
    public class SecurityProperties {
        /** imooc.security.browser 路径下的配置会被映射到该配置类中 */
        private BrowserProperties browser = new BrowserProperties();
        private ValidateCodeProperties code = new ValidateCodeProperties();

修改处理逻辑处

    cn.mrcode.imooc.springsecurity.securitycore.validate.code.ValidateCodeController
    
    private ImageCode createImageCode(HttpServletRequest request) throws IOException {
        ImageCodeProperties imageProperties = securityProperties.getCode().getImage();
        // 先从请求中获取,然后从配置中获取
        // 如果配置中的没有被覆盖则是默认配置
        int width = ServletRequestUtils.getIntParameter(request, "width", imageProperties.getWidth());
        int height = ServletRequestUtils.getIntParameter(request, "height", imageProperties.getHeight());
        int length = ServletRequestUtils.getIntParameter(request, "length", imageProperties.getLength());
        int expireIn = ServletRequestUtils.getIntParameter(request, "expireIn", imageProperties.getExpireIn());
        String code = RandomStringUtils.randomNumeric(length);
        BufferedImage image = createImageCode(width, height, code);
        return new ImageCode(image, code, expireIn);
    }

验证码校验拦截的接口可配置

实现思路:

  1. 提供url拦截地址配置属性
  2. 过滤器中获取配置的属性,并且循环匹配

增加url配置属性

    public class ImageCodeProperties {
        private int width = 67;
        private int height = 23;
        private int length = 4;  // 验证码长度
        private int expireIn = 60;  // 过期时间
        private String url;  // 要验证的接口url路径,逗号隔开

过滤器中对目标url进行匹配逻辑

    public class ValidateCodeFilter extends OncePerRequestFilter implements InitializingBean {
        // 在初始化本类的地方进行注入
        // 一般在配置security http的地方进行添加过滤器
        private AuthenticationFailureHandler failureHandler;
        private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
    
        // 由初始化的地方传递进来
        private SecurityProperties securityProperties;
        // 存储所有需要拦截的url
        private Set<String> urls;
    
        private AntPathMatcher pathMatcher = new AntPathMatcher();
    
        /**
         * org.springframework.beans.factory.InitializingBean 保证在其他属性都设置完成后,有beanFactory调用
         * 但是在这里目前还是需要初始化处调用该方法
         * @throws ServletException
         */
        @Override
        public void afterPropertiesSet() throws ServletException {
            super.afterPropertiesSet();
            String url = securityProperties.getCode().getImage().getUrl();
            String[] configUrl = StringUtils.split(url, ",");
            urls = Stream.of(configUrl).collect(Collectors.toSet());
            urls.add("/authentication/form"); // 登录请求
        }
    
        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
            // 为登录请求,并且为post请求
            boolean action = false;
            for (String url : urls) {
                // org.springframework.util.AntPathMatcher 能匹配spring中的url模式
                // 支持通配符路径那种
                if (pathMatcher.match(url, request.getRequestURI())) {
                    action = true;
                }
            }
            if (action) {
                try {
                    validate(request);
                } catch (ValidateCodeException e) {
                    failureHandler.onAuthenticationFailure(request, response, e);
                    return;
                }
            }
            filterChain.doFilter(request, response);
        }

原配置中心进行属性注入

    cn.mrcode.imooc.springsecurity.securitybrowser.BrowserSecurityConfig
    // 有三个configure的方法,这里使用http参数的
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 最简单的修改默认配置的方法
        // 在v5+中,该配置(表单登录)应该是默认配置了
        // basic登录(也就是弹框登录的)应该是v5-的版本默认
    
        ValidateCodeFilter validateCodeFilter = new ValidateCodeFilter();
        validateCodeFilter.setFailureHandler(myAuthenticationFailureHandler);
        validateCodeFilter.setSecurityProperties(securityProperties);  // 注入配置属性类
        validateCodeFilter.afterPropertiesSet(); // 初始化url配置

测试:security-demo/application.yml

    imooc:
      security:
        browser:
         # loginPage: /demo-signIn.html
         # loginType: REDIRECT
          loginType: JSON
        code:
          image:
            width: 100
            height: 50
            url: /order,/user/*   # 对订单和所有user路径进行验证码拦截 */

验证码的生成逻辑可配

思路:逻辑可配,就是抽象成接口,然后由客户端提供

这里的思路很强大,让我学习到了 spring中的默认配置是怎么实现的

提供一个生成图片信息的接口

    package cn.mrcode.imooc.springsecurity.securitycore.validate.code;
    
    import javax.servlet.http.HttpServletRequest;
    import java.io.IOException;
    
    public interface ValidateCodeGenerate {
        ImageCode generate(HttpServletRequest request) throws IOException;
    }

实现默认图片生成接口类

    cn.mrcode.imooc.springsecurity.securitycore.validate.code.ImageCodeGenerate
    
    public class ImageCodeGenerate implements ValidateCodeGenerate {
        private ImageCodeProperties imageProperties;
    
        public ImageCodeGenerate(ImageCodeProperties imageProperties) {
            this.imageProperties = imageProperties;
        }
    
        @Override
        public ImageCode generate(HttpServletRequest request) throws IOException {
            return createImageCode(request);
        }
    
        public ImageCode createImageCode(HttpServletRequest request) throws IOException {
            int width = ServletRequestUtils.getIntParameter(request, "width", imageProperties.getWidth());
            int height = ServletRequestUtils.getIntParameter(request, "height", imageProperties.getHeight());
            int length = ServletRequestUtils.getIntParameter(request, "length", imageProperties.getLength());
            int expireIn = ServletRequestUtils.getIntParameter(request, "expireIn", imageProperties.getExpireIn());
            String code = RandomStringUtils.randomNumeric(length);
            BufferedImage image = createImageCode(width, height, code);
            return new ImageCode(image, code, expireIn);
        }
    
        ...后面的就是具体的工具类代码 不贴了

增加配置类,初始化图片生成器实例;这个是重点!!

    package cn.mrcode.imooc.springsecurity.securitycore.validate.code;
    
    import cn.mrcode.imooc.springsecurity.securitycore.properties.SecurityProperties;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    public class ValidateCodeConfig {
        @Autowired
        private SecurityProperties securityProperties;
    
        @Bean
        // spring 容器中如果存在imageCodeGenerate的bean就不会再初始化该bean了
        // 条件注解
        @ConditionalOnMissingBean(name = "imageCodeGenerate")
        public ValidateCodeGenerate imageCodeGenerate() {
            ImageCodeGenerate imageCodeGenerate = new ImageCodeGenerate(securityProperties.getCode().getImage());
            return imageCodeGenerate;
        }
    }

之前调用处修改成调用接口

    public class ValidateCodeController {
        public static final String SESSION_KEY = "SESSION_KEY_IMAGE_CODE";
        private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
    
        @Autowired
        private SecurityProperties securityProperties;
    
        @Autowired  // 使用生成接口处理信息
        private ValidateCodeGenerate validateCodeGenerate;

最后测试思路:

  1. 先保证重构后的代码功正常工作
  2. 在demo项目中 实现兵初始化一个ValidateCodeGenerate实现类,调试看看是否走进了我们自己的生成逻辑

小结

本章知识点
* SessionStrategy
* @ConditionalOnMissingBean(name = "imageCodeGenerate") bean条件注解
* AntPathMatcher 路径工具类


Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。

它的内容包括:

  • 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
  • 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
  • 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
  • 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
  • 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
  • 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
  • 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
  • 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw

目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:

想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询

同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。

阅读全文