zoukankan      html  css  js  c++  java
  • SpringSceurity(3)---图形验证码功能实现

    SpringSceurity(3)---图形验证码功能实现

    有关springSceurity之前有写过两篇文章:

    1、SpringSecurity(1)---认证+授权代码实现

    2、SpringSecurity(2)---记住我功能实现

    这篇我们来讲图形验证码功能实现。

    一、思路

    我整理下springSceurity整合图形验证码的大致思路:

    1、首先对于验证码本身而言,应该有三部分组成 1、存放验证码的背景图片 2、验证码 3、验证码的有效时间。
    
    2、对于springSceurity而言,验证码的执行校验顺序肯定是在UsernamePasswordAuthenticationFilter之前的,因为如果验证码都不对,那么
    根本都不需要验证账号密码。所以我们需要自定义一个验证码过滤器,并且配置在UsernamePasswordAuthenticationFilter之前执行。
    
    3、对于获取验证码的接口,肯定是不需要进行认证限制的。
    
    4、对于获取验证码的接口的时候,需要把该验证码信息+当前浏览器的SessonId绑定在一起存在Seesion中,为了后面校验的时候通过SessonId
    去取这个验证码信息。
    
    5、登陆请求接口,除了带上用户名和密码之外,还需要带上验证码信息。在进入验证码过滤器的时候,首先通过SessonId获取存在Sesson中的
    验证码信息,拿到验证码信息之后首先还要校验该验证码是否在有效期内。之后再和当前登陆接口带来的验证码进行对比,如果一致,那么当前
    验证码这一关就过了,就开始验证下一步账号和密码是否正确了。
    

    整个流程大致就是这样。下面现在是具体代码,然后进行测试。


    二、代码展示

    这里只展示一些核心代码,具体完整项目会放到github上。

    1、ImageCodeProperties

    这个是一个bean实体,是一个图形验证码的默认配置。

    @Data
    public class ImageCodeProperties {
        /**
         * 验证码宽度
         */
        private int width = 67;
        /**
         * 验证码高度
         */
        private int height = 23;
        /**
         * 验证码长度
         */
        private int length = 4;
        /**
         * 验证码过期时间
         */
        private int expireIn = 60;
        /**
         * 需要验证码的请求url字符串,用英文逗号隔开
         */
        private String url = "/login";
    
    }
    

    2、ImageCode

    这个是图片验证码的完整信息,也会将这个完整信息存放于Sesson中。

    图片验证码信息 由三部分组成 :

    1.图片信息(长、宽、背景色等等)。2.code就是真正的验证码,用来验证用。3.该验证码的有效时间。

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class ImageCode {
    
        private BufferedImage image;
        private String code;
        private LocalDateTime expireTime;
    
        public ImageCode(BufferedImage image, String code, int expireIn) {
            this.image = image;
            this.code = code;
            this.expireTime = LocalDateTime.now().plusSeconds(expireIn);
        }
        /**
         * 校验是否过期
         */
        public boolean isExpired() {
            return LocalDateTime.now().isAfter(expireTime);
        }
    }
    

    3、ValidateCodeGeneratorService

    获取验证码的接口

    public interface ValidateCodeGeneratorService {
    
        /**
         * 生成图片验证码
         *
         * @param request 请求
         * @return ImageCode实例对象
         */
        ImageCode generate(ServletWebRequest request);
    }
    

    4、ImageCodeGeneratorServiceImpl

    获取图片验证码的接口的实现类

    @Data
    public class ImageCodeGeneratorServiceImpl implements ValidateCodeGeneratorService {
    
        private static final String IMAGE_WIDTH_NAME = "width";
        private static final String IMAGE_HEIGHT_NAME = "height";
        private static final Integer MAX_COLOR_VALUE = 255;
    
        private ImageCodeProperties imageCodeProperties;
    
        @Override
        public ImageCode generate(ServletWebRequest request) {
            //设置图片的宽度和高度
            int width = ServletRequestUtils.getIntParameter(request.getRequest(), IMAGE_WIDTH_NAME, imageCodeProperties.getWidth());
            int height = ServletRequestUtils.getIntParameter(request.getRequest(), IMAGE_HEIGHT_NAME, imageCodeProperties.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);
            }
    
            // 生成数字验证码
            StringBuilder sRand = new StringBuilder();
            for (int i = 0; i < imageCodeProperties.getLength(); i++) {
                String rand = String.valueOf(random.nextInt(10));
                sRand.append(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.toString(), imageCodeProperties.getExpireIn());
        }
    
        /**
         * 生成随机背景条纹
         *
         * @param fc 前景色
         * @param bc 背景色
         * @return RGB颜色
         */
        private Color getRandColor(int fc, int bc) {
            Random random = new Random();
            if (fc > MAX_COLOR_VALUE) {
                fc = MAX_COLOR_VALUE;
            }
            if (bc > MAX_COLOR_VALUE) {
                bc = MAX_COLOR_VALUE;
            }
            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);
        }
    }
    

    5、ValidateCodeController

    获取验证码的请求接口。

    @RestController
    public class ValidateCodeController {
    
        /**
         * 前缀
         */
       public static final String SESSION_KEY = "SESSION_KEY_IMAGE_CODE";
        private static final String FORMAT_NAME = "JPEG";
       @Autowired
        private ValidateCodeGeneratorService imageCodeGenerator;
        private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
    
        @GetMapping("/code/image")
        public void createCode(HttpServletRequest request, HttpServletResponse response) throws IOException {
            // 第一步:根据请求生成一个图形验证码对象
            ImageCode imageCode = imageCodeGenerator.generate(new ServletWebRequest(request));
            // 第二步:将图形验证码对象存到session中,第一个参数可以从传入的请求中获取session
            sessionStrategy.setAttribute(new ServletRequestAttributes(request), SESSION_KEY, imageCode);
            // 第三步:将生成的图片写到接口的响应中
            ImageIO.write(imageCode.getImage(), FORMAT_NAME, response.getOutputStream());
        }
    
    }
    

    到这里,我们可用请求获取图片验证码的信息了。接下来我们就要登陆请求部分的代码。

    6、ValidateCodeFilter

    自定义过滤器,这里面才是核心的代码,首先继承OncePerRequestFilter(直接继承Filter也是可用的),实现InitializingBean是为了初始化一些初始数据。

    这里走的逻辑就是把存在session中的图片验证码和当前请求的验证码进行比较,如果相同则放行,否则直接抛出异常

    @Data
    @Slf4j
    @Component
    public class ValidateCodeFilter extends OncePerRequestFilter implements InitializingBean {
    
        private static final String SUBMIT_FORM_DATA_PATH = "/login";
    
        /**
         * 失败处理器
         */
        @Autowired
        private AuthenctiationFailHandler authenctiationFailHandler;
        /**
         * 验证码属性类
         */
        @Autowired
        private ImageCodeProperties imageCodeProperties;
        
        /**
         * 存放需要走验证码请求url
         */
        private Set<String> urls = new HashSet<>();
    
        /**
         * 处理session工具类
         */
        private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
        /**
         * 正则配置工具
         */
        private final AntPathMatcher antPathMatcher = new AntPathMatcher();
        /**
         * 在初始化bean的时候都会执行该方法
         */
        @Override
        public void afterPropertiesSet() throws ServletException {
            super.afterPropertiesSet();
            String[] configUrls = StringUtils.split(imageCodeProperties.getUrl(), ",");
            // 登录的链接是必须要进行验证码验证的
            urls.addAll(Arrays.asList(configUrls));
        }
    
        /**
         * 拦截请求进来的方法。
         */
        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
            boolean action = false;
            for (String url : urls) {
                // 如果实际访问的URL可以与用户在imageCodeProperties中url配置的相同,那么就进行验证码校验
                log.info("request.getRequestURI = {}",request.getRequestURI());
                if (antPathMatcher.match(url, request.getRequestURI())) {
                    action = true;
                }
            }
            //说明需要校验
            if (action) {
                try {
                    validate(new ServletWebRequest(request));
                } catch (ValidateCodeException e) {
                    authenctiationFailHandler.onAuthenticationFailure(request, response, e);
                    return;
                }
            }
    
            //进入下一个过滤器
            filterChain.doFilter(request, response);
        }
    
        /**
         * 验证码校验逻辑
         *
         */
        private void validate(ServletWebRequest request) throws ServletRequestBindingException {
            // 从session中获取图片验证码
            ImageCode imageCodeInSession = (ImageCode) sessionStrategy.getAttribute(request, ValidateCodeController.SESSION_KEY);
    
            // 从请求中获取用户填写的验证码
            String imageCodeInRequest = ServletRequestUtils.getStringParameter(request.getRequest(), "imageCode");
            if (StringUtils.isBlank(imageCodeInRequest)) {
                throw new ValidateCodeException("验证码不能为空");
            }
            if (null == imageCodeInSession) {
                throw new ValidateCodeException("验证码不存在");
            }
            if (imageCodeInSession.isExpired()) {
                sessionStrategy.removeAttribute(request, ValidateCodeController.SESSION_KEY);
                throw new ValidateCodeException("验证码已过期");
            }
    
            log.info("session中获取的验证码={},sessionId ={}",imageCodeInSession.getCode(),request.getSessionId());
            log.info("登陆操作传来的验证码={}",imageCodeInRequest);
            if (!StringUtils.equalsIgnoreCase(imageCodeInRequest, imageCodeInSession.getCode())) {
                throw new ValidateCodeException("验证码不匹配");
            }
            // 验证成功,删除session中的验证码
            sessionStrategy.removeAttribute(request, ValidateCodeController.SESSION_KEY);
        }
    }
    
    

    7、WebSecurityConfig

    SpringSecurity的Java 配置类也需要做一点点改动。那就是需要设置ValidateCodeFilter要在UsernamePasswordAuthenticationFilter之前进行拦截过滤。

    到这里整个图形验证码的功能就开发完成了,具体代码放在github上下面进行测试。


    三、测试

    说明下我这里懒的写前端相关代码了,所以直接用posman用请求来获取验证码,获取验证码之后再进行登陆操作。

    1、获取验证码

    这里验证码code为:1848

    2、登陆成功

    输入的验证码也是1848,显示登陆成功。

    3、登陆失败

    因为配置的时候图片验证码有效期为60秒,所以在我们获取验证码后,过60秒再去登陆,就能发现,验证码已过期。

    整个验证码功能大致就是这样。


    Github地址 : spring-boot-security-study-03



    别人骂我胖,我会生气,因为我心里承认了我胖。别人说我矮,我就会觉得好笑,因为我心里知道我不可能矮。这就是我们为什么会对别人的攻击生气。
    攻我盾者,乃我内心之矛(19)
    
  • 相关阅读:
    6.BLE---数据传输
    5.BLE---报文
    4.BLE---广播信道防冲突与数据信道选择
    3.BLE---信道与功率
    Ubuntu 安装exe 软件
    Zephyr ubuntu 环境搭建
    ES6语法(一)let 和 const 命令
    Vue(二十三)vuex + axios + 缓存 运用 (以登陆功能为例)
    Vue(二十二)vuex小案例(官网计数案例整合)
    Vue(二十一)使用express模拟接口数据
  • 原文地址:https://www.cnblogs.com/qdhxhz/p/12791083.html
Copyright © 2011-2022 走看看