美文网首页
4.重构图形验证码

4.重构图形验证码

作者: 呆叔么么 | 来源:发表于2019-12-23 15:02 被阅读0次
    重构图形验证码的参数 图新验证码参数配置

    1.实现验证码的基本参数可配置

    修改application.yml文件
    imooc-security/imooc-security-demo/src/main/resources/application.yml

    # 配置自定义登录页面
    imooc:
      security:
        code:
          image:
            width: 70
            height: 30
            length: 4
            expire: 120
    

    创建ImageCodeProperties配置文件
    com.imooc.security.core.properties.ImageCodeProperties

    package com.imooc.security.core.properties;
    
    /**
     * @Author:LovingLiu
     * @Description:
     * @Date:Created in 2019-12-23
     */
    public class ImageCodeProperties {
        private int width = 67;
        private int height = 23;
        private int length = 4;
        private int expire = 60;
    
        public int getWidth() {
            return width;
        }
    
        public void setWidth(int width) {
            this.width = width;
        }
    
        public int getHeight() {
            return height;
        }
    
        public void setHeight(int height) {
            this.height = height;
        }
    
        public int getLength() {
            return length;
        }
    
        public void setLength(int length) {
            this.length = length;
        }
    
        public int getExpire() {
            return expire;
        }
    
        public void setExpire(int expire) {
            this.expire = expire;
        }
    }
    

    创建ValidateCodeProperties配置文件
    com.imooc.security.core.properties.ValidateCodeProperties

    package com.imooc.security.core.properties;
    
    /**
     * @Author:LovingLiu
     * @Description:
     * @Date:Created in 2019-12-23
     */
    public class ValidateCodeProperties {
        private ImageCodeProperties image = new ImageCodeProperties();
    
        public ImageCodeProperties getImage() {
            return image;
        }
    
        public void setImage(ImageCodeProperties image) {
            this.image = image;
        }
    }
    

    修改SecurityProperties配置文件
    com.imooc.security.core.properties.SecurityProperties

    package com.imooc.security.core.properties;
    
    import org.springframework.boot.context.properties.ConfigurationProperties;
    
    /**
     * @Author:LovingLiu
     * @Description:
     * @Date:Created in 2019-12-22
     */
    @ConfigurationProperties(prefix = "imooc.security")
    public class SecurityProperties {
        private BrowserProperties browser = new BrowserProperties();
        private ValidateCodeProperties code = new ValidateCodeProperties();
    
        public BrowserProperties getBrowser() {
            return browser;
        }
    
        public void setBrowser(BrowserProperties browser) {
            this.browser = browser;
        }
    
        public ValidateCodeProperties getCode() {
            return code;
        }
    
        public void setCode(ValidateCodeProperties code) {
            this.code = code;
        }
    }
    

    修改ValidateCodeController
    com.imooc.security.core.validate.code.ValidateCodeController

    package com.imooc.security.core.validate.code;
    
    import com.imooc.security.core.properties.SecurityProperties;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.social.connect.web.HttpSessionSessionStrategy;
    import org.springframework.social.connect.web.SessionStrategy;
    import org.springframework.web.bind.ServletRequestUtils;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    import org.springframework.web.context.request.ServletWebRequest;
    
    import javax.imageio.ImageIO;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.awt.*;
    import java.awt.image.BufferedImage;
    import java.io.IOException;
    import java.util.Random;
    
    /**
     * @Author:LovingLiu
     * @Description:
     * @Date:Created in 2019-12-22
     */
    @RestController
    public class ValidateCodeController  {
        public static final String SESSION_KEY = "SESSION_KEY_IMAGE_CODE";
        // session 操作
        private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
    
        // 引入验证码的配置信息
        @Autowired
        private SecurityProperties securityProperties;
    
        @GetMapping("/code/image")
        public void createCode(HttpServletRequest request, HttpServletResponse response) throws IOException {
            // 1.生成验证码
            ImageCode imageCode = createImageCode(request);
            // 2.将随机数存放在Session中
            sessionStrategy.setAttribute(new ServletWebRequest(request),SESSION_KEY,imageCode);
            // 3.将图片写回响应
            ImageIO.write(imageCode.getImage(),"JPEG",response.getOutputStream());
        }
    
        private ImageCode createImageCode(HttpServletRequest request){
            /**
             * 为应用级别的配置
             * 1.先从请求中获取参数
             * 2.若是没有再去配置文件中读取
             * ServletRequestUtils HTTP请求的使用
             *
             */
            int width = ServletRequestUtils.getIntParameter(request,"width",securityProperties.getCode().getImage().getWidth());
            int height = ServletRequestUtils.getIntParameter(request,"height",securityProperties.getCode().getImage().getHeight());
            BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
    
            Graphics g = image.getGraphics();
    
            Random random = new Random();
    
            g.setColor(getRandColor(200, 250));
            g.fillRect(0, 0, width, height);
            g.setFont(new Font("Times New Roman", Font.ITALIC, 20));
            g.setColor(getRandColor(160, 200));
            for (int i = 0; i < 155; i++) {
                int x = random.nextInt(width);
                int y = random.nextInt(height);
                int xl = random.nextInt(12);
                int yl = random.nextInt(12);
                g.drawLine(x, y, x + xl, y + yl);
            }
    
            String sRand = "";
            for (int i = 0; i < securityProperties.getCode().getImage().getLength(); i++) {
                String rand = String.valueOf(random.nextInt(10));
                sRand += rand;
                g.setColor(new Color(20 + random.nextInt(110), 20 + random.nextInt(110), 20 + random.nextInt(110)));
                g.drawString(rand, 13 * i + 6, 16);
            }
    
            g.dispose();
    
            return new ImageCode(image, sRand, securityProperties.getCode().getImage().getExpire());
        }
        private Color getRandColor(int fc, int bc) {
            Random random = new Random();
            if (fc > 255) {
                fc = 255;
            }
            if (bc > 255) {
                bc = 255;
            }
            int r = fc + random.nextInt(bc - fc);
            int g = fc + random.nextInt(bc - fc);
            int b = fc + random.nextInt(bc - fc);
            return new Color(r, g, b);
        }
    }
    

    2.实现拦截参数可配

    修改application.yml文件

    # 配置自定义登录页面
    imooc:
      security:
        code:
          image:
            url: /user,/user/* 
    

    修改配置文件ImageCodeProperties
    com.imooc.security.core.properties.ImageCodeProperties

    package com.imooc.security.core.properties;
    
    /**
     * @Author:LovingLiu
     * @Description:
     * @Date:Created in 2019-12-23
     */
    public class ImageCodeProperties {
        private int width = 67;
        private int height = 23;
        private int length = 4;
        private int expire = 60;
        private String url = ""; // 拦截的请求URL
    
        public int getWidth() {
            return width;
        }
    
        public void setWidth(int width) {
            this.width = width;
        }
    
        public int getHeight() {
            return height;
        }
    
        public void setHeight(int height) {
            this.height = height;
        }
    
        public int getLength() {
            return length;
        }
    
        public void setLength(int length) {
            this.length = length;
        }
    
        public int getExpire() {
            return expire;
        }
    
        public void setExpire(int expire) {
            this.expire = expire;
        }
    
        public String getUrl() {
            return url;
        }
    
        public void setUrl(String url) {
            this.url = url;
        }
    }
    

    修改ValidateCodeFilter过滤器
    com.imooc.security.core.validate.code.ValidateCodeFilter
    修改验证url的逻辑

    package com.imooc.security.core.validate.code;
    
    import com.imooc.security.core.properties.SecurityProperties;
    import org.apache.commons.lang.StringUtils;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.InitializingBean;
    import org.springframework.security.web.authentication.AuthenticationFailureHandler;
    import org.springframework.social.connect.web.HttpSessionSessionStrategy;
    import org.springframework.social.connect.web.SessionStrategy;
    import org.springframework.util.AntPathMatcher;
    import org.springframework.web.bind.ServletRequestBindingException;
    import org.springframework.web.bind.ServletRequestUtils;
    import org.springframework.web.context.request.ServletWebRequest;
    import org.springframework.web.filter.OncePerRequestFilter;
    
    import javax.servlet.FilterChain;
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.util.HashSet;
    import java.util.Set;
    
    /**
     * @Author:LovingLiu
     * @Description:
     * 自定义验证码的过滤器,根据请求判断是哪一种验证
     * OncePerRequestFilter保证每次请求只被调用一次
     * InitializingBean接口的实现方法afterPropertiesSet()会在bean在初始化后自动调用且在init-method之前调用
     * @Date:Created in 2019-12-22
     */
    public class ValidateCodeFilter extends OncePerRequestFilter implements InitializingBean {
        private Logger log = LoggerFactory.getLogger(getClass());
    
        private AuthenticationFailureHandler authenticationFailureHandler;
    
        private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
    
        private Set<String> urls = new HashSet<>(); // 可配置的可拦截的请求URL
    
        private SecurityProperties securityProperties; // 由Set方法传入
    
        private AntPathMatcher antPathMatcher = new AntPathMatcher(); // 工具类
    
        @Override
        public void afterPropertiesSet() throws ServletException {
            super.afterPropertiesSet();
            String[] configUrls = StringUtils.split(securityProperties.getCode().getImage().getUrl(),",");
            for (String config: configUrls) {
                urls.add(config);
                log.info("【afterPropertiesSet自动调用】=> {}",config);
            }
            urls.add("/authentication/form");
        }
    
        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
            boolean action = false;
            for (String url: urls) {
                if(antPathMatcher.match(url,request.getRequestURI())){
                    action = true;
                }
            }
            if(action){
                try{
                    valicate(new ServletWebRequest(request));
                }catch (ValidateCodeException e){
                    authenticationFailureHandler.onAuthenticationFailure(request,response,e);
                    return;// 直接返回
                }
    
            }
            //继续执行沿着Filter链执行
            filterChain.doFilter(request, response);
        }
    
        private void valicate(ServletWebRequest request) throws ServletRequestBindingException {
            ImageCode codeInSession = (ImageCode) sessionStrategy.getAttribute(request,ValidateCodeController.SESSION_KEY);
    
            // 获取请求参数
            String codeInRequest;
            try {
                codeInRequest = ServletRequestUtils.getStringParameter(request.getRequest(),
                        "imageCode");
            } catch (ServletRequestBindingException e) {
                throw new ValidateCodeException("获取验证码的值失败");
            }
    
            if (StringUtils.isBlank(codeInRequest)) {
                throw new ValidateCodeException("验证码的值不能为空");
            }
    
            if (codeInSession == null) {
                throw new ValidateCodeException("验证码不存在");
            }
    
            if (codeInSession.isExpried()) {
                sessionStrategy.removeAttribute(request, ValidateCodeController.SESSION_KEY);
                throw new ValidateCodeException("验证码已过期");
            }
    
            if (!StringUtils.equals(codeInSession.getCode(), codeInRequest)) {
                throw new ValidateCodeException("验证码不匹配");
            }
    
            sessionStrategy.removeAttribute(request, ValidateCodeController.SESSION_KEY);
        }
    
        public void setAuthenticationFailureHandler(AuthenticationFailureHandler authenticationFailureHandler) {
            this.authenticationFailureHandler = authenticationFailureHandler;
        }
    
        public SecurityProperties getSecurityProperties() {
            return securityProperties;
        }
    
        public void setSecurityProperties(SecurityProperties securityProperties) {
            this.securityProperties = securityProperties;
        }
    }
    

    修改配置文件BrowserSecurityConfig
    com.imooc.security.browser.BrowserSecurityConfig
    注入urls属性和securityProperties

    package com.imooc.security.browser;
    
    import com.imooc.security.core.properties.SecurityProperties;
    import com.imooc.security.core.validate.code.ValidateCodeFilter;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    import org.springframework.security.crypto.password.PasswordEncoder;
    import org.springframework.security.web.authentication.AuthenticationFailureHandler;
    import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
    import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
    
    /**
     * @Author:LovingLiu
     * @Description: Security安全配置的适配器
     * @Date:Created in 2019-12-18
     */
    @Configuration
    @EnableWebSecurity
    public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {
        @Autowired
        private SecurityProperties securityProperties;
    
        @Autowired
        private AuthenticationSuccessHandler imoocAuthenticationSuccessHandler;
    
        @Autowired
        private AuthenticationFailureHandler imoocAuthenticationFailureHandler;
    
        @Bean
        public PasswordEncoder passwordEncoder(){
            return new BCryptPasswordEncoder(); // 推荐使用 BCrypt 的加密的形式
        }
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            ValidateCodeFilter validateCodeFilter = new ValidateCodeFilter();
            validateCodeFilter.setAuthenticationFailureHandler(imoocAuthenticationFailureHandler);
            validateCodeFilter.setSecurityProperties(securityProperties);
            validateCodeFilter.afterPropertiesSet(); // 设置配置的属性
    
            http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class). // 添加自定义过滤器
                    formLogin(). // 声明验证方式为表单登录
                    loginPage("/authentication/require"). // 自定义登录界面
                    loginProcessingUrl("/authentication/form"). // 指定接收username/password的请求路径
                    successHandler(imoocAuthenticationSuccessHandler). // 使用自定义成功处理器
                    failureHandler(imoocAuthenticationFailureHandler). // 使用自定义失败处理器
                    and(). //
                    authorizeRequests(). // 对请求进行授权
                    antMatchers("/authentication/require","/authentication/form","/code/image",securityProperties.getBrowser().getLoginPage()).permitAll().// 配置不进行认证请求
                    anyRequest(). // 任意请求
                    authenticated(). // 都需要身份认证
                    and().
                    csrf().disable(); // 关闭跨站请求防护
        }
    }
    

    InitializingBean接口的使用
    1、Spring为bean提供了两种初始化bean的方式,实现InitializingBean接口,实现afterPropertiesSet方法,或者在配置文件中通过init-method指定,两种方式可以同时使用。

    2、实现InitializingBean接口是直接调用afterPropertiesSet方法,比通过反射调用init-method指定的方法效率要高一点,但是init-method方式消除了对spring的依赖。

    3、如果调用afterPropertiesSet方法时出错,则不调用init-method指定的方法。

    3.验证码生成逻辑可配置

    非常重要,这里将会体现出一个增量编程,即:在不改变当前代码所拥有的功能下,进行功能的递增。
    创建接口ValidateCodeGenerator
    com.imooc.security.core.validate.code.ValidateCodeGenerator
    定义生成验证码图片的方法

    package com.imooc.security.core.validate.code;
    
    import javax.servlet.http.HttpServletRequest;
    
    /**
     * @Author:LovingLiu
     * @Description: 自定义生成验证码的逻辑
     * @Date:Created in 2019-12-23
     */
    public interface ValidateCodeGenerator {
        ImageCode createImageCode(HttpServletRequest request);
    }
    

    创建验证码生成器的默认实现ImageCodeGenerator
    com.imooc.security.core.validate.code.ImageCodeGenerator
    实际上就是将原来生成验证码的代码抽离出来

    package com.imooc.security.core.validate.code;
    
    import com.imooc.security.core.properties.SecurityProperties;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.ServletRequestUtils;
    
    import javax.servlet.http.HttpServletRequest;
    import java.awt.*;
    import java.awt.image.BufferedImage;
    import java.util.Random;
    
    /**
     * @Author:LovingLiu
     * @Description: 自定义生成验证码的逻辑(默认实现)
     * 注意: 这里不写@Component 与后面的 @ConditionalOnMissingBean 相辅相成
     * @Date:Created in 2019-12-23
     */
    public class ImageCodeGenerator implements ValidateCodeGenerator {
    
        // 引入验证码的配置信息
        @Autowired
        private SecurityProperties securityProperties;
    
        @Override
        public ImageCode createImageCode(HttpServletRequest request){
            /**
             * 为应用级别的配置
             * 1.先从请求中获取参数
             * 2.若是没有再去配置文件中读取
             * ServletRequestUtils HTTP请求的使用
             *
             */
            int width = ServletRequestUtils.getIntParameter(request,"width",securityProperties.getCode().getImage().getWidth());
            int height = ServletRequestUtils.getIntParameter(request,"height",securityProperties.getCode().getImage().getHeight());
            BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
    
            Graphics g = image.getGraphics();
    
            Random random = new Random();
    
            g.setColor(getRandColor(200, 250));
            g.fillRect(0, 0, width, height);
            g.setFont(new Font("Times New Roman", Font.ITALIC, 20));
            g.setColor(getRandColor(160, 200));
            for (int i = 0; i < 155; i++) {
                int x = random.nextInt(width);
                int y = random.nextInt(height);
                int xl = random.nextInt(12);
                int yl = random.nextInt(12);
                g.drawLine(x, y, x + xl, y + yl);
            }
    
            String sRand = "";
            for (int i = 0; i < securityProperties.getCode().getImage().getLength(); i++) {
                String rand = String.valueOf(random.nextInt(10));
                sRand += rand;
                g.setColor(new Color(20 + random.nextInt(110), 20 + random.nextInt(110), 20 + random.nextInt(110)));
                g.drawString(rand, 13 * i + 6, 16);
            }
    
            g.dispose();
    
            return new ImageCode(image, sRand, securityProperties.getCode().getImage().getExpire());
        }
        private Color getRandColor(int fc, int bc) {
            Random random = new Random();
            if (fc > 255) {
                fc = 255;
            }
            if (bc > 255) {
                bc = 255;
            }
            int r = fc + random.nextInt(bc - fc);
            int g = fc + random.nextInt(bc - fc);
            int b = fc + random.nextInt(bc - fc);
            return new Color(r, g, b);
        }
    
        public SecurityProperties getSecurityProperties() {
            return securityProperties;
        }
    
        public void setSecurityProperties(SecurityProperties securityProperties) {
            this.securityProperties = securityProperties;
        }
    }
    

    新增ValidateCodeBeanConfig配置类

    package com.imooc.security.core.validate.code;
    
    import com.imooc.security.core.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;
    
    /**
     * @Author:LovingLiu
     * @Description: 生成验证码逻辑的相关配置
     * @Date:Created in 2019-12-23
     */
    @Configuration
    public class ValidateCodeBeanConfig {
        @Autowired
        private SecurityProperties securityProperties;
    
        /**
         * @ConditionalOnMissingBean注解作用在@bean定义上
         * 可以给该注解传入参数例如@ConditionOnMissingBean(name = "example")
         * 这个表示如果name为“example”的bean存在,这该注解修饰的代码块不执行
         * @return
         */
        @Bean
        @ConditionalOnMissingBean(name = "imageCodeGenerator")
        public ValidateCodeGenerator imageCodeGenerator(){
            ImageCodeGenerator codeGenerator = new ImageCodeGenerator();
            codeGenerator.setSecurityProperties(securityProperties);
            return codeGenerator;
        }
    }
    

    修改ValidateCodeController获取验证码逻辑
    com.imooc.security.core.validate.code.ValidateCodeController

    package com.imooc.security.core.validate.code;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.social.connect.web.HttpSessionSessionStrategy;
    import org.springframework.social.connect.web.SessionStrategy;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    import org.springframework.web.context.request.ServletWebRequest;
    
    import javax.imageio.ImageIO;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    
    /**
     * @Author:LovingLiu
     * @Description: 获取验证码图片的Controller
     * @Date:Created in 2019-12-22
     */
    @RestController
    public class ValidateCodeController  {
        public static final String SESSION_KEY = "SESSION_KEY_IMAGE_CODE";
        // session 操作
        private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
    
        // 生成验证码逻辑可配置 改造
        @Autowired
        private ValidateCodeGenerator validateCodeGenerator;
    
        @GetMapping("/code/image")
        public void createCode(HttpServletRequest request, HttpServletResponse response) throws IOException {
            // 1.生成验证码
            ImageCode imageCode = validateCodeGenerator.createImageCode(request);
            // 2.将随机数存放在Session中
            sessionStrategy.setAttribute(new ServletWebRequest(request),SESSION_KEY,imageCode);
            // 3.将图片写回响应
            ImageIO.write(imageCode.getImage(),"JPEG",response.getOutputStream());
        }
    }
    

    床架自定义生成验证码的逻辑DemoImageCodeGenerator
    com.imooc.code.DemoImageCodeGenerator

    package com.imooc.code;
    
    import com.imooc.security.core.validate.code.ImageCode;
    import com.imooc.security.core.validate.code.ValidateCodeGenerator;
    import org.springframework.stereotype.Component;
    import org.springframework.web.bind.ServletRequestUtils;
    
    import javax.servlet.http.HttpServletRequest;
    import java.awt.image.BufferedImage;
    
    /**
     * @Author:LovingLiu
     * @Description: 自定义生成验证码的逻辑
     * @Component("imageCodeGenerator") 覆盖默认的实现
     * @Date:Created in 2019-12-23
     */
    @Component("imageCodeGenerator")
    public class DemoImageCodeGenerator implements ValidateCodeGenerator {
        @Override
        public ImageCode createImageCode(HttpServletRequest request) {
            System.out.println("更高级的图形验证码的生成代码");
            int width = ServletRequestUtils.getIntParameter(request,"width",150);
            int height = ServletRequestUtils.getIntParameter(request,"height",100);
            BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
    
    
            return new ImageCode(image, "1111", 60);
        }
    }
    

    相关文章

      网友评论

          本文标题:4.重构图形验证码

          本文链接:https://www.haomeiwen.com/subject/ewbanctx.html