springboot——security实现验证码:自定义过滤器

验证码(CAPTCHA)的全称是Complete Automated Public Turing test to tell Computers And Humans Apart,翻译过来就是“全自动区分计算机和人类的图灵测试”。通俗地讲,验证码就是为了防止恶意用户暴力重试而设置的。不管是用户注册,用户登录,还是论坛发帖,如果不加以限制,一旦某些恶意用户利用计算机发起无限重试,就容易导致系统遭到破坏。

一、Maven配置

<dependencies>
	<dependency>
		<groupId>org.springframework.bootgroupId>
		<artifactId>spring-boot-starter-webartifactId>
	dependency>
	<dependency>
		<groupId>org.springframework.bootgroupId>
		<artifactId>spring-boot-starter-data-jdbcartifactId>
	dependency>
	<dependency>
		<groupId>org.springframework.bootgroupId>
		<artifactId>spring-boot-starter-securityartifactId>
	dependency>
	<dependency>
		<groupId>org.springframework.bootgroupId>
		<artifactId>spring-boot-starter-actuatorartifactId>
	dependency>
	<dependency>
		<groupId>com.baomidougroupId>
		<artifactId>mybatis-plus-boot-starterartifactId>
		<version>3.2.0version>
	dependency>
	<dependency>
		<groupId>mysqlgroupId>
		<artifactId>mysql-connector-javaartifactId>
		<scope>runtimescope>
	dependency>
	<dependency>
		<groupId>org.projectlombokgroupId>
		<artifactId>lombokartifactId>
		<optional>trueoptional>
	dependency>
	
	<dependency>
		<groupId>com.github.pengglegroupId>
		<artifactId>kaptchaartifactId>
		<version>2.3.2version>
	dependency>
	<dependency>
		<groupId>org.springframework.bootgroupId>
		<artifactId>spring-boot-starter-testartifactId>
		<scope>testscope>
	dependency>
	<dependency>
		<groupId>org.springframework.securitygroupId>
		<artifactId>spring-security-testartifactId>
		<scope>testscope>
	dependency>
dependencies>

二、验证码生成代码

@SpringBootApplication
@MapperScan("com.example.securitymybatis.dao")
public class SecurityMybatisApplication {

    public static void main(String[] args) {
        SpringApplication.run(SecurityMybatisApplication.class, args);
    }

    @Bean
    public Producer producer() { // 配置验证码参数
        Properties properties = new Properties();
        properties.setProperty(KAPTCHA_IMAGE_WIDTH, "150");
        properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "50");
        properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_STRING, "0123456789");
        properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "4");
        DefaultKaptcha kaptcha = new DefaultKaptcha();
        kaptcha.setConfig(new Config(properties));
        return kaptcha;
    }
}
@Controller
public class CaptchaController {
    @Autowired
    private Producer producer;

    @GetMapping("/captcha.jpg")
    public void getCaptcha(HttpServletRequest request, HttpServletResponse response) throws IOException {
        response.setContentType("image/jpeg");
        String captchaText = producer.createText();
        request.getSession().setAttribute("captcha", captchaText);
        BufferedImage image = producer.createImage(captchaText);
        try(ServletOutputStream outputStream = response.getOutputStream()) {
            ImageIO.write(image, "jpg", outputStream);
            outputStream.flush();
        }
    }
}

三、自定义过滤器

这个对验证码的过滤器需放在UsernamePasswordAuthenticationFilter之前,可继承OncePerRequestFilter确保一次请求只会通过一次该过滤器

/**
 * 验证码验证
 */
public class VerificationCodeFilter extends OncePerRequestFilter {
    private AuthenticationFailureHandler failureHandler;

    public VerificationCodeFilter(AuthenticationFailureHandler failureHandler) {
        this.failureHandler = failureHandler;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // 登录请求不校验验证码
        if (Objects.equals(request.getRequestURI(), "/login")) {
            String captcha = request.getParameter("captcha");
            HttpSession session = request.getSession();
            String expect = (String) session.getAttribute("captcha");
            if (!StringUtils.isEmpty(expect)) {
                // 随手清除验证码,无论是失败,还是成功。客户端应在登录失败时刷新验证码
                session.removeAttribute("captcha");
            }
            if (!Objects.equals(captcha, expect)) {
                try {
                    throw new VerifationCodeException("验证码错误!");
                } catch (VerifationCodeException e) {
                    failureHandler.onAuthenticationFailure(request, response, e);
                }
            } else {
                filterChain.doFilter(request, response);
            }
        } else {
            filterChain.doFilter(request, response);
        }
    }
}

四、security配置

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/admin/api/**").hasRole("ADMIN")
                .antMatchers("/user/api/**").hasRole("USER")
                .antMatchers("/app/api/**", "/captcha.jpg", "/login.html").permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .loginPage("/login.html")
                .loginProcessingUrl("/login")
                .failureHandler(authenticationFailureHandler())
                .and().sessionManagement().maximumSessions(1)
                .and().and()
                .csrf().disable();
        // 将过滤器配置在UsernamePasswordAuthenticationFilter之前
        http.addFilterBefore(new VerificationCodeFilter(authenticationFailureHandler()), UsernamePasswordAuthenticationFilter.class);
    }
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new MessageDigestPasswordEncoder("MD5");
    }

    @Bean
    public ObjectMapper objectMapper() {
        return new ObjectMapper();
    }

    @Bean
    public AuthenticationFailureHandler authenticationFailureHandler() {//验证失败返回JSON格式信息
        return (request, response, exception) -> {
            Map<String, Object> map = new HashMap<>();
            map.put("code", 401);
            map.put("message", "验证码错误");
            response.setContentType("application/json;charset=utf-8");
            PrintWriter out = response.getWriter();
            out.write(new ObjectMapper().writeValueAsString(map));
            out.flush();
            out.close();
        };
    }
}

启动springboot,打开http://localhost:8080/admin/api/hello访问
springboot——security实现验证码:自定义过滤器_第1张图片
点击login,返回{“code”:401,“message”:“验证码错误”}
改为填写正确的账号/密码和验证码
springboot——security实现验证码:自定义过滤器_第2张图片
点击login,返回hello, admin!

你可能感兴趣的:(springboot)