zoukankan      html  css  js  c++  java
  • springboot验证码重构

    考虑到有多种验证机制(例如:常见的图片验证码,手机短信验证码,邮箱验证码)

    所以在项目中对验证码进行代码重构,使之更加的具有可扩展性,低耦合性,此项目基于springboot

    1.首先Controller层

    @RestController
    public class ValidateCodeController {
    
        @Autowired
        private ValidateCodeProcessorHolder holder;
    
        @GetMapping(SecurityConstants.DEFAULT_VALIDATE_CODE_URL_PREFIX+"/{type}")
        public void createCode(HttpServletRequest request, HttpServletResponse response, @PathVariable String type) throws Exception {
            holder.findValidateCodeProcessor(type).create(request,response);
        }
    }

    前端发起类似 /code/image这样的请求,将验证码类型获取到,从hold中找到哪个验证码处理器来进行处理

    2.验证码管家

    package club.wenfan.youtube.validate;
    
    import club.wenfan.youtube.validate.exception.ValidateCodeException;
    import club.wenfan.youtube.validate.processor.ValidateCodeProcessor;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    import java.util.Map;
    
    
    /**
     * @author:wenfan
     * @description:
     * @data: 2019/1/22 9:31
     */
    @Component
    public class ValidateCodeProcessorHolder {
    
        @Autowired
        private Map<String,ValidateCodeProcessor> validateCodeProcessors;
    
        private Logger log = LoggerFactory.getLogger(getClass());
    
        public ValidateCodeProcessor findValidateCodeProcessor(ValidateCodeType type){
            return findValidateCodeProcessor(type.toString().toLowerCase());
    
        }
    
        public ValidateCodeProcessor findValidateCodeProcessor(String type){
            String name=type.toLowerCase() + ValidateCodeProcessor.class.getSimpleName();
            ValidateCodeProcessor processor=validateCodeProcessors.get(name);  //通过类型查找出用那个验证码处理器
            log.info("验证码处理器"+name);
            if(processor == null){
                throw new ValidateCodeException("验证码处理器"+name+"不存在");
            }
            return processor;
        }
    
    
    }
    ValidateCodeProcessorHolder类

    特别说明一下

    @Autowired
    private Map<String,ValidateCodeProcessor> validateCodeProcessors;
    采用这样的Map Bean注入方式,注入时将所有Bean的名字和类型作为Map注入进来
    然后选择时通过Bean的名字来确定用哪个验证码处理器来完成。
    3.ValidateCodeProcessor接口
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    /**
     * @author:wenfan
     * @description:
     * @data: 2019/1/21 12:03
     */
    public interface ValidateCodeProcessor {
    
        /**
         *  验证码放入session时的前缀
         * @author wenfan
         * @date
         * @param
         * @return
         */
        String SESSION_KEY_PREFIX="SESSION_KEY_FOR_CODE_";
    
        /**
         *  创建校验码
         * @author wenfan
         */
        void create(HttpServletRequest request, HttpServletResponse response) throws Exception;
    
        void validate(HttpServletRequest request,HttpServletResponse response);
    
    }

    4.将验证码处理器的生成、保存、验证、验证抽象出来,单独将发送写成一个抽象方法,用具体的验证码处理来实现此方法

    import club.wenfan.youtube.validate.ValidateCodeType;
    import club.wenfan.youtube.validate.code.ValidateCode;
    import club.wenfan.youtube.validate.code.ValidateCodeGenerator;
    import club.wenfan.youtube.validate.exception.ValidateCodeException;
    import club.wenfan.youtube.validate.processor.ValidateCodeProcessor;
    import org.apache.commons.lang.StringUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.ServletRequestBindingException;
    import org.springframework.web.bind.ServletRequestUtils;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.util.Map;
    
    /**
     *
     * @author wenfan
     * @date
     * @param
     * @return
     */
    public abstract class AbstractValidateCodeProcessor<C extends ValidateCode> implements ValidateCodeProcessor {
    
        @Autowired
        private Map<String, ValidateCodeGenerator> validateCodeGenerators;
    
        /**
         *
         * @author wenfan
         * @date
         * @param
         * @return
         */
        @Override
        public void create(HttpServletRequest request, HttpServletResponse response) throws Exception {
            C validateCode = generate(request);
            System.out.println(request.getRequestURI());
            System.out.println(validateCode.getCode());
            save(request, validateCode);
            send(request,response, validateCode);
        }
    
        /**
         * 生成校验码
         *
         * @param request
         * @return
         */
        @SuppressWarnings("unchecked")
        private C generate(HttpServletRequest request)  {
            String type = getValidateCodeType(request).toString().toLowerCase();
            String generatorName = type + ValidateCodeGenerator.class.getSimpleName();
            ValidateCodeGenerator validateCodeGenerator = validateCodeGenerators.get(generatorName);
            if (validateCodeGenerator == null) {
                throw new ValidateCodeException("验证码生成器" + generatorName + "不存在");
            }
            return (C) validateCodeGenerator.CreateCode(request);
        }
    
        /**
         * 保存校验码
         *
         * @param request
         * @param validateCode
         */
        private void save(HttpServletRequest request, C validateCode) {
            ValidateCode code = new ValidateCode(validateCode.getCode(),validateCode.getExpireTime());
            System.out.println(getSessionKey(request));
            request.getSession(true).setAttribute(getSessionKey(request), code);
            System.out.println(request.getSession().getAttribute(getSessionKey(request)));
        }
    
        /**
         * 构建验证码放入session时的key
         *
         * @param request
         * @return
         */
        private String getSessionKey(HttpServletRequest request) {
            return SESSION_KEY_PREFIX + getValidateCodeType(request).toString().toUpperCase();
        }
    
        /**
         * 发送校验码,由子类实现
         *
         * @param request
         * @param validateCode
         * @throws Exception
         */
        protected abstract void send(HttpServletRequest request,HttpServletResponse response, C validateCode) throws Exception;
    
        /**
         * 根据请求的url获取校验码的类型
         *
         * @param request
         * @return
         */
        private ValidateCodeType getValidateCodeType(HttpServletRequest request) {
            String type = StringUtils.substringBefore(getClass().getSimpleName(), "CodeProcessor");
            return ValidateCodeType.valueOf(type.toUpperCase());
        }
    
        @SuppressWarnings("unchecked")
        @Override
        public void validate(HttpServletRequest request,HttpServletResponse response) {
    
            ValidateCodeType processorType = getValidateCodeType(request);
            String sessionKey = getSessionKey(request);
            System.out.println("sessionKey="+sessionKey);
            C codeInSession = (C) request.getSession(false).getAttribute(sessionKey);
            System.out.println(codeInSession==null?"codeinsession为null":"codeinsession不为null");
            String codeInRequest;
            try {
                codeInRequest = ServletRequestUtils.getStringParameter(request,
                        processorType.getParamNameOnValidate());
            } catch (ServletRequestBindingException e) {
                throw new ValidateCodeException("获取验证码的值失败");
            }
    
            if (StringUtils.isBlank(codeInRequest)) {
                throw new ValidateCodeException(processorType + "验证码的值不能为空");
            }
    
            if (codeInSession == null) {
                throw new ValidateCodeException(processorType + "验证码不存在");
            }
    
            if (codeInSession.isExpired()) {
                request.getSession().removeAttribute(sessionKey);
                throw new ValidateCodeException(processorType + "验证码已过期");
            }
    
            if (!StringUtils.equals(codeInSession.getCode(), codeInRequest)) {
                throw new ValidateCodeException(processorType + "验证码不匹配");
            }
            request.getSession().removeAttribute(sessionKey);
        }
    
    }
    AbstractValidateCodeProcessor

    5.具体的验证码生成器只有发送方法,这里只贴了图片验证码处理器

    @Component("imageValidateCodeProcessor")
    public class ImageCodeProcessor extends AbstractValidateCodeProcessor<ImgCode> {
    
        @Override
        protected void send(HttpServletRequest request, HttpServletResponse response, ImgCode imgCode) throws Exception {
            ImageIO.write(imgCode.getImage(),"JPEG",response.getOutputStream());
        }
    
    }

    6.在处理器抽象法中也运用方法Map Bean 的方式来选择验证码生成器的类型

    public interface ValidateCodeGenerator {
        ValidateCode CreateCode(HttpServletRequest request);
    }

    7.具体的验证码生成器

    import club.wenfan.youtube.properties.SecurityProperties;
    import club.wenfan.youtube.validate.code.ImgCode;
    import club.wenfan.youtube.validate.code.ValidateCodeGenerator;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    import org.springframework.web.bind.ServletRequestUtils;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.awt.*;
    import java.awt.image.BufferedImage;
    import java.util.Random;
    
    /**
     * @author:wenfan
     * @description:
     * @data: 2019/1/1 18:33
     */
    
    @Component("imageValidateCodeGenerator")
    public class ImgCodeGenerator implements ValidateCodeGenerator {
    
        @Autowired
        private SecurityProperties securityProperties;
    
        private Logger log = LoggerFactory.getLogger(getClass());
    
        @Override
        public ImgCode CreateCode(HttpServletRequest request) {
            //可以在请求中加入 width/height get参数  当没有参数时从用户的自定义的配置文件中读取
            int width =ServletRequestUtils.getIntParameter(request,"width",securityProperties.getCode().getImg().getWidth());// 定义图片的width
            int height = ServletRequestUtils.getIntParameter(request," height",securityProperties.getCode().getImg().getHeight());// 定义图片的height
            int codeCount =securityProperties.getCode().getImg().getCodeCount();// 定义图片上显示验证码的个数
            int expiredTime = securityProperties.getCode().getImg(). getExpiredTime();
            int xx = 18;
            int fontHeight = 20;
            int codeY = 27;
            char[] codeSequence = { '0','1', '2', '3', '4','5', '6', '7', '8', '9' };
    
            // 定义图像buffer
            BufferedImage buffImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
            Graphics gd = buffImg.getGraphics();
            // 创建一个随机数生成器类
            Random random = new Random();
            // 将图像填充为白色
            gd.setColor(Color.WHITE);
            gd.fillRect(0, 0, width, height);
    
            // 创建字体,字体的大小应该根据图片的高度来定。
            Font font = new Font("Fixedsys", Font.BOLD, fontHeight);
            // 设置字体。
            gd.setFont(font);
    
            // 画边框。
            gd.setColor(Color.BLACK);
            gd.drawRect(0, 0, width - 1, height - 1);
    
            // 随机产生40条干扰线,使图象中的认证码不易被其它程序探测到。
            gd.setColor(Color.BLACK);
            for (int i = 0; i < 30; i++) {
                int x = random.nextInt(width);
                int y = random.nextInt(height);
                int xl = random.nextInt(12);
                int yl = random.nextInt(12);
                gd.drawLine(x, y, x + xl, y + yl);
            }
    
            // randomCode用于保存随机产生的验证码,以便用户登录后进行验证。
            StringBuffer randomCode = new StringBuffer();
            int red = 0, green = 0, blue = 0;
    
            // 随机产生codeCount数字的验证码。
            for (int i = 0; i <codeCount; i++) {
                // 得到随机产生的验证码数字。
                String code = String.valueOf(codeSequence[random.nextInt(10)]);
                // 产生随机的颜色分量来构造颜色值,这样输出的每位数字的颜色值都将不同。
                red = random.nextInt(255);
                green = random.nextInt(255);
                blue = random.nextInt(255);
    
                // 用随机产生的颜色将验证码绘制到图像中。
                gd.setColor(new Color(red, green, blue));
                gd.drawString(code, (i + 1) * xx, codeY);
    
                // 将产生的四个随机数组合在一起。
                randomCode.append(code);
    
            }
            log.info("产生验证码"+randomCode.toString());
            return new ImgCode(buffImg,randomCode.toString(),expiredTime);
        }
    
        public SecurityProperties getSecurityProperties() {
            return securityProperties;
        }
    
        public void setSecurityProperties(SecurityProperties securityProperties) {
            this.securityProperties = securityProperties;
        }
    }
    View Code

    8.如果想更换系统中默认的验证码处理器可以采用一种可扩展的bean注入方式

    @Configuration
    public class ValidateCodeBeanConfig {
    
        @Autowired
        private SecurityProperties securityProperties;
    
    
        /**
         *  当容器中没有imageValidateCodeGenerator 这个Bean的时候,会主动配置下面的默认Bean
         *  以增量的形式实现变化不
         *
         * @author wenfan
         * @date
         * @param
         * @return
         */
        @Bean
        @ConditionalOnMissingBean(name = "imageValidateCodeGenerator")
        public ValidateCodeGenerator imgCodeGenerator(){
            ImgCodeGenerator codeGenerator=new ImgCodeGenerator();
            codeGenerator.setSecurityProperties(securityProperties);
            return codeGenerator;
        }
    
    
        /**
         *  class形式和 name的方式相同
         * @author wenfan
         * @date
         * @param
         * @return
         */
    
        @Bean
        @ConditionalOnMissingBean(SmsCodeSender.class)
        public SmsCodeSender smsCodeSender(){
            return new SmsCodeSenderImpl();
        }
    
    }

    如果不想用默认验证码生成器,之需要注册bean,bean名字为imageValidateCodeGenerator

    9.验证码过滤

    写一个过滤器继承OncePerRequestFilter

    @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    
            response.setContentType("application/json;charset=utf-8");
            ValidateCodeType type=getValidateCodeType(request);
            if(type !=null){
                logger.info("校验请求(" + request.getRequestURI() + ")中的验证码,验证码类型" + type);
                try {
                    validateCodeProcessorHolder.findValidateCodeProcessor(type).validate(request,response);
                    logger.info("验证码通过");
                } catch (ValidateCodeException e) {
                    authenticationFailureHandler.onAuthenticationFailure(request,response,e);
                    return;
                }
            }
                filterChain.doFilter(request,response);
        }

    发起请求时,验证码处理器管家来决定哪一个处理器来处理

    10.将过滤器添加到配置中

    @Component("validateCodeSecurityConfig")
    public class ValidateCodeSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain,HttpSecurity> {
    
        @Autowired
        private Filter vaildateCodeFilter;
    
        @Override
        public void configure(HttpSecurity http) throws Exception {
            http.addFilterBefore(vaildateCodeFilter,AbstractPreAuthenticatedProcessingFilter.class);
        }
    }

    感谢阅读

  • 相关阅读:
    Power BI for Office 365(八)共享查询
    Power BI for Office 365(七) Power BI站点
    Power BI for Office 365(六)Power Map简介
    Power BI for Office 365(五)Power View第二部分
    Power BI for Office 365(四)Power View第一部分
    Power BI for Office 365(三)Power Pivot
    Power BI for Office 365(二)Power Query
    java 继承、重载、重写与多态
    Android 热修复方案Tinker(一) Application改造
    阿里最新热修复Sophix与QQ超级补丁和Tinker的实现与总结
  • 原文地址:https://www.cnblogs.com/outxiao/p/10409779.html
Copyright © 2011-2022 走看看