zoukankan      html  css  js  c++  java
  • SpringSecurity(六): 实现图形验证码认证

    1.SpringSecurity并没有给我们提供图形验证码,因为spring security他的基本原理就是一个过滤器链。对于springSceurity而言,验证码的执行校验顺序肯定是在UsernamePasswordAuthenticationFilter之前的,因为如果验证码都不对,那么 根本都不需要验证账号密码。在这个链上我们可以加入自己写的过滤器。我们在UsernamePasswordAuthticationFilter前加一个自定义的过滤器。 extends OncePerRequestFilter。实现:doFilterInternal方法。

            <!--图形验证码-->
            <dependency>
                <groupId>com.github.penggle</groupId>
                <artifactId>kaptcha</artifactId>
            </dependency>        

    2.生成验证码配置类

    import com.google.code.kaptcha.Constants;
    import com.google.code.kaptcha.impl.DefaultKaptcha;
    import com.google.code.kaptcha.util.Config;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    import java.util.Properties;
    
    /**
     * 生成验证码配置类
     */
    @Configuration
    public class KaptchaVerifyCodeConfig {
        @Bean
        public DefaultKaptcha getDefaultKaptcha(){
            DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
            Properties properties = new Properties();
            properties.setProperty(Constants.KAPTCHA_BORDER, "yes");
            properties.setProperty(Constants.KAPTCHA_BORDER_COLOR, "192,192,192");
            properties.setProperty(Constants.KAPTCHA_IMAGE_WIDTH, "110");
            properties.setProperty(Constants.KAPTCHA_IMAGE_HEIGHT, "36");
            properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_FONT_COLOR, "blue");
            properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_FONT_SIZE, "28");
            properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_FONT_NAMES, "宋体");
            properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "4");
            // 图片效果
            properties.setProperty(Constants.KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy");
            Config config = new Config(properties);
            defaultKaptcha.setConfig(config);
            return defaultKaptcha;
        }
    }

    3.验证码生成处理接口

    import com.google.code.kaptcha.impl.DefaultKaptcha;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    
    import javax.imageio.ImageIO;
    import javax.servlet.ServletOutputStream;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import javax.servlet.http.HttpSession;
    import java.awt.image.BufferedImage;
    import java.io.IOException;
    
    
    @Controller
    public class VerifyCodeController {
        Logger logger = LoggerFactory.getLogger(getClass());
        public static final String SESSION_VERIFY_CODE = "SESSION_KEY_VERIFY_CODE";
    
        @Autowired
        DefaultKaptcha defaultKaptcha;
    
        /**
         * 验证码请求接口
         * @param request
         * @param response
         * @throws IOException
         */
        @RequestMapping(value = "/code/image", method = RequestMethod.GET)
        public void getImageCode(HttpServletRequest request, HttpServletResponse response) throws IOException {
            //1.获取验证码字符串
            String code = defaultKaptcha.createText();
            logger.info("生产的验证码:"+code);
            // 2. 字符串把它放到session中
            HttpSession session = request.getSession();
            session.setAttribute(SESSION_VERIFY_CODE , code);
            // 3. 获取验证码图片
            BufferedImage image = defaultKaptcha.createImage(code);
            // 4. 将验证码图片把它写出去
            // 禁止图片缓存
            response.setHeader("Pragma","no-cache");
            response.setHeader("Cache-Control","no-cache");
            response.setDateHeader("Expires",0);
            ServletOutputStream out = response.getOutputStream();
            ImageIO.write(image, "jpg", out);
        }
    
    }

    4.前端获取验证码

     <img onclick="this.src='/code/image?'+Math.random()" src="/code/image" alt="验证码"   />

     5.验证码校验过滤器

    /**
     *
     * 验证码校验过滤器
     * OncePerRequestFilter: 所有请求之前被调用一次
     */
    @Component("imageCodeValidateFilter")
    public class VerifyCodeValidateFilter extends OncePerRequestFilter {
        @Autowired
        SecurityProperties securityProperties;
    
        @Autowired
        CustomAuthenticationFailureHandler customAuthenticationFailureHandler;
    
        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
            // 1. 如果是post方式 的登录请求,则校验输入的验证码是否正确
            if (securityProperties.getLoginProcessingUrl().equals(request.getRequestURI())&&request.getMethod().equalsIgnoreCase("post")){
    
                try {
                     //校验验证码合法性
                    validate(request);
                } catch (AuthenticationException  e) {
                    //将验证失败的抛出的异常交给自定义认证失败处理器处理异常
                    customAuthenticationFailureHandler.onAuthenticationFailure(request,response,e);
                    return;
                }
    
            }
            filterChain.doFilter(request,response);
        }
    
        private void validate(HttpServletRequest request) {
            //先获取session中的验证码
            String sessionImageCode = (String) request.getSession().getAttribute(VerifyCodeController.SESSION_VERIFY_CODE);
            //获取用户输入的验证码
            String inputCode = request.getParameter("code");
            if (StringUtils.isBlank(inputCode)){
                throw new VerifyCodeExpection("验证码不能为空");
            }
            if (!inputCode.equalsIgnoreCase(sessionImageCode)){
                 throw new VerifyCodeExpection("验证码输入错误");
            }
        }
    }
    /**
     * 自定义验证码异常类
     */
    public class VerifyCodeExpection extends AuthenticationException {
        public VerifyCodeExpection(String msg, Throwable t) {
            super(msg, t);
        }
    
        public VerifyCodeExpection(String msg) {
            super(msg);
        }
    }

    6.在认证流程安全配置类SpringSecurityConfig中加入图形验证码校验 过滤器(需要把自定义的过滤器加到UsernamePasswordAuthenticationFilter前面去。)

    @Autowired
        CustomAuthenticationSuccessHandler customAuthenticationSuccessHandler;
        @Autowired
        CustomAuthenticationFailureHandler customAuthenticationFailureHandler;
        @Autowired
        VerifyCodeValidateFilter verifyCodeValidateFilter;
        /**
         * 资源权限配置(过滤器链):
         * 1、被拦截的资源
         * 2、资源所对应的角色权限
         * 3、定义认证方式:httpBasic 、httpForm
         * 4、定制登录页面、登录请求地址、错误处理方式
         * 5、自定义 spring security 过滤器
         *
         * @param http
         * @throws Exception
         */
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            //http.httpBasic()//采用httpBasic 认证方式
            /*http.formLogin()
                    .loginPage("/login/page")// 交给 /login/page 响应认证(登录)页面
                    .loginProcessingUrl("/login/form")  // 登录表单提交处理Url, 默认是 /login
                    .usernameParameter("name") // 默认用户名的属性名是 username
                    .passwordParameter("pwd") // 默认密码的属性名是 password
                    .and()
                    .authorizeRequests()//认证请求
                    .antMatchers("/login/page").permitAll()//自定义登录页不需要认证
                    .anyRequest().authenticated();// 所有进入应用的HTTP请求都要进行认证*/
    
            http.addFilterBefore(verifyCodeValidateFilter, UsernamePasswordAuthenticationFilter.class)//将校验过滤器 imageCodeValidateFilter 添加到 UsernamePasswordAuthenticationFilter 前面
                    .formLogin()
                    .loginPage(securityProperties.getLoginPage())// 交给 /login/page 响应认证(登录)页面
                    .loginProcessingUrl(securityProperties.getLoginProcessingUrl())  // 登录表单提交处理Url, 默认是 /login
                    .usernameParameter(securityProperties.getUsernameParameter()) // 默认用户名的属性名是 username
                    .passwordParameter(securityProperties.getPasswordParameter()) // 默认密码的属性名是 password
                    .successHandler(customAuthenticationSuccessHandler)//自定义认证成功处理器
                    .failureHandler(customAuthenticationFailureHandler)//自定义认证失败处理器
                    .and()
                    .authorizeRequests()//认证请求
                    .antMatchers(securityProperties.getLoginPage(),"/code/image").permitAll()//自定义登录页不需要认证,生成图片验证码也不需要验证
                    .anyRequest().authenticated();// 所有进入应用的HTTP请求都要进行认证
        }

     完整代码地址:https://gitee.com/zhechaochao/security-parent.git

  • 相关阅读:
    实用JS代码
    javascript-table出现滚动条表格自动对齐
    Javascript-关于for in和forEach
    Javascript-关于break、continue、return语句
    如何编写可怕的Java代码?
    请停止编写这么多的for循环!
    如何优雅地在Stack Overflow提问?
    为什么阿里巴巴Java开发手册中强制要求不要在foreach循环里进行元素的remove和add操作?
    Java异常处理只有Try-Catch吗?
    看完这篇还不会用Git,那我就哭了!
  • 原文地址:https://www.cnblogs.com/yscec/p/14238851.html
Copyright © 2011-2022 走看看