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

posted on 2021-01-06 01:33  西门夜说  阅读(414)  评论(0编辑  收藏  举报