spring-security-oauth2(四) 图片验证码

图片验证码

  1. 图片验证码生成接口
  2. 认证流程加入图片验证码校验
  3. 图片验证码重构

1.图片验证码生成接口

  1. 调用com.google.code.kaptcha.Producer生成图片验证码
  2. 将随机数存到session缓存中
  3. 将生成的图片写到响应流中

图片验证码封装类  ImageCaptchaVo

package com.rui.tiger.auth.core.captcha;

import lombok.Data;

import java.awt.image.BufferedImage;
import java.time.LocalDateTime;

/**
 * 图片验证码信息对象
 * @author CaiRui
 * @Date 2018/12/9 18:03
 */
@Data
public class ImageCaptchaVo {
    /**
     * 图片验证码
     */
    private BufferedImage image;
    /**
     *  验证码
     */
    private String code;
    /**
     * 失效时间 这个通常放在缓存中或维护在数据库中
     */
    private LocalDateTime expireTime;

    public ImageCaptchaVo(BufferedImage image, String code, int expireAfterSeconds){
        this.image = image;
        this.code = code;
        //多少秒后
        this.expireTime = LocalDateTime.now().plusSeconds(expireAfterSeconds);
    }

    public ImageCaptchaVo(BufferedImage image, String code, LocalDateTime expireTime){
        this.image = image;
        this.code = code;
        this.expireTime = expireTime;
    }

    /**
     * 是否失效
     * @return
     */
    public boolean isExpried() {
        return LocalDateTime.now().isAfter(expireTime);
    }

}

图片验证码服务类  CaptchaController  注意这个路径/captcha/image要在BrowserSecurityConfig配置中放行

package com.rui.tiger.auth.core.captcha;

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 CaiRui
 * @date 2018-12-10 12:13
 */
@RestController
public class CaptchaController {

	public static final String IMAGE_CAPTCHA_SESSION_KEY="image_captcha_session_key";
	private static final String FORMAT_NAME="JPEG";

	@Autowired
	private CaptchaGenerate captchaGenerate;
    //spring session 工具类
	private SessionStrategy sessionStrategy=new HttpSessionSessionStrategy();

	/**
	 * 获取图片验证码
	 * @param request
	 * @param response
	 * @throws IOException
	 */
	@GetMapping("/captcha/image")
	public  void createKaptcha(HttpServletRequest request, HttpServletResponse response) throws IOException {
		//1.接口生成验证码
		ImageCaptchaVo imageCaptcha= (ImageCaptchaVo) captchaGenerate.generate();
		//2.保存到session中
		sessionStrategy.setAttribute(new ServletWebRequest(request), IMAGE_CAPTCHA_SESSION_KEY, imageCaptcha);
		//3.写到响应流中
		response.setHeader("Cache-Control", "no-store, no-cache");// 没有缓存
		response.setContentType("image/jpeg");
		ImageIO.write(imageCaptcha.getImage(),FORMAT_NAME,response.getOutputStream());
	}

}

  CaptchaGenerate 接口及其实现类

package com.rui.tiger.auth.core.captcha;

/**
 * 验证码生成接口
 *
 * @author CaiRui
 * @date 2018-12-10 12:03
 */
public interface CaptchaGenerate {
	/**
	 * 生成图片验证码
	 *
	 * @return
	 */
	ImageCaptchaVo generate();
}
package com.rui.tiger.auth.core.captcha;

import com.google.code.kaptcha.Producer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.awt.image.BufferedImage;

/**
 * 图片验证码生成接口
 *
 * @author CaiRui
 * @date 2018-12-10 12:07
 */
@Service("imageCaptchaGenerate")
public class ImageCaptchaGenerate implements CaptchaGenerate {

	@Autowired
	private Producer producer;//config bean中配置

	@Override
	public ImageCaptchaVo generate() {
		String code = producer.createText();
		BufferedImage bufferedImage = producer.createImage(code);
		return new ImageCaptchaVo(bufferedImage, code, 60 * 5);//5分钟过期
	}
}

Producer配置类 KaptchaGenerateConfig

package com.rui.tiger.auth.core.config;

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;

/**
 * 验证码生成配置类
 * @author CaiRui
 * @date 2018-12-10 12:09
 */
@Configuration
public class KaptchaGenerateConfig {

	//TODO 配置项放在配置文件中
	@Bean
	public DefaultKaptcha producer() {
		Properties properties = new Properties();
		properties.put("kaptcha.border", "no");
		properties.put("kaptcha.textproducer.font.color", "black");
		properties.put("kaptcha.textproducer.char.space", "5");
		Config config = new Config(properties);
		DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
		defaultKaptcha.setConfig(config);
		return defaultKaptcha;
	}
}

2.认证流程加入图片验证码校验

通过上篇的源码分析spring-security原理,其实就是过滤器链上的各个过滤器协同工作,思路如下:

  • 编写我们的自定义图片验证码过滤器
  • 将它放在UsernamePasswordAuthenticationFilter表单过滤器之前

验证码过滤器 

package com.rui.tiger.auth.core.captcha;

import lombok.Getter;
import lombok.Setter;
import org.apache.commons.lang.StringUtils;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.social.connect.web.HttpSessionSessionStrategy;
import org.springframework.social.connect.web.SessionStrategy;
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;

/**
 * 图片验证码过滤器
 * OncePerRequestFilter 过滤器只会调用一次
 *
 * @author CaiRui
 * @date 2018-12-10 12:23
 */
public class CaptchaFilter extends OncePerRequestFilter {

	//一般在配置类中进行注入
	@Setter
	@Getter
	private AuthenticationFailureHandler failureHandler;

	private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();

	@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
		//表单登录的post请求
		if (StringUtils.equals("/authentication/form", request.getRequestURI())
				&& StringUtils.equalsIgnoreCase("post", request.getMethod())) {
			try {
				validate(request);
			} catch (CaptchaException captchaException) {
				//失败调用我们的自定义失败处理器
				failureHandler.onAuthenticationFailure(request, response, captchaException);
				//后续流程终止
				return;
			}
		}
		filterChain.doFilter(request, response);

	}

	/**
	 * 图片验证码校验
	 *
	 * @param request
	 */
	private void validate(HttpServletRequest request) throws ServletRequestBindingException {
		// 拿到之前存储的imageCode信息
		ServletWebRequest swr = new ServletWebRequest(request);
		ImageCaptchaVo imageCodeInSession = (ImageCaptchaVo) sessionStrategy.getAttribute(swr, CaptchaController.IMAGE_CAPTCHA_SESSION_KEY);
		String codeInRequest = ServletRequestUtils.getStringParameter(request, "imageCode");

		if (StringUtils.isBlank(codeInRequest)) {
			throw new CaptchaException("验证码的值不能为空");
		}
		if (imageCodeInSession == null) {
			throw new CaptchaException("验证码不存在");
		}
		if (imageCodeInSession.isExpried()) {
			sessionStrategy.removeAttribute(swr, CaptchaController.IMAGE_CAPTCHA_SESSION_KEY);
			throw new CaptchaException("验证码已过期");
		}
		if (!StringUtils.equals(imageCodeInSession.getCode(), codeInRequest)) {
			throw new CaptchaException("验证码不匹配");
		}
		//验证通过 移除缓存
		sessionStrategy.removeAttribute(swr, CaptchaController.IMAGE_CAPTCHA_SESSION_KEY);
	}
}

自定义验证码异常 

package com.rui.tiger.auth.core.captcha;

import org.springframework.security.core.AuthenticationException;

/**
 * 自定义验证码异常
 * @author CaiRui
 * @date 2018-12-10 12:43
 */
public class CaptchaException extends AuthenticationException {

	public CaptchaException(String msg, Throwable t) {
		super(msg, t);
	}

	public CaptchaException(String msg) {
		super(msg);
	}
}

将过滤器加入到浏览器权限配置中

package com.rui.tiger.auth.browser.config;

import com.rui.tiger.auth.core.authentication.TigerAuthenticationFailureHandler;
import com.rui.tiger.auth.core.authentication.TigerAuthenticationSuccessHandler;
import com.rui.tiger.auth.core.captcha.CaptchaFilter;
import com.rui.tiger.auth.core.properties.SecurityProperties;
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.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

/**
 * 浏览器security配置类
 *
 * @author CaiRui
 * @date 2018-12-4 8:41
 */
@Configuration
public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private SecurityProperties securityProperties;
    @Autowired
    private TigerAuthenticationFailureHandler tigerAuthenticationFailureHandler;
    @Autowired
    private TigerAuthenticationSuccessHandler tigerAuthenticationSuccessHandler;

    /**
     * 密码加密解密
     *
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //加入图片验证码过滤器
        CaptchaFilter captchaFilter=new CaptchaFilter();
        captchaFilter.setFailureHandler(tigerAuthenticationFailureHandler);
        //图片验证码放在认证之前
        http.addFilterBefore(captchaFilter, UsernamePasswordAuthenticationFilter.class)
                .formLogin()
                .loginPage( "/authentication/require")//自定义登录请求
                .loginProcessingUrl("/authentication/form")//自定义登录表单请求
                .successHandler(tigerAuthenticationSuccessHandler)
                .failureHandler(tigerAuthenticationFailureHandler)
                .and()
                .authorizeRequests()
                .antMatchers(securityProperties.getBrowser().getLoginPage(),
                        "/authentication/require","/captcha/image")//此路径放行 否则会陷入死循环
                .permitAll()
                .anyRequest()
                .authenticated()
                .and()
                .csrf().disable()//跨域关闭
        ;
    }

}

前段标准登录界面加入验证码改造

<html>
<head>
    <meta charset="UTF-8">
    <title>登录</title>
</head>
<body>
<h2>标准登录页面</h2>
<h3>表单登录</h3>
<form action="/authentication/form" method="post">
    <table>
        <tr>
            <td>用户名:</td>
            <td><input type="text" name="username"></td>
        </tr>
        <tr>
            <td>密码:</td>
            <td><input type="password" name="password"></td>
        </tr>
        <tr>
            <td>图形验证码:</td>
            <td>
                <input type="text" name="imageCode">
                <img src="/captcha/image">
            </td>
        </tr>
        <tr>
            <td colspan="2"><button type="submit">登录</button></td>
        </tr>
    </table>
</form>
</body>
</html>

ok  我们项目重新启动下 来看下自定义的验证码过滤器是否可用,直接浏览器输入我们的登录地址http://localhost:8070/tiger-login.html

可以看到图片验证码已经成功显示出来了,我们来看看验证逻辑是否可用,试下不输入验证码

这是因为我们自定义的失败处理器,打印了全部的错误堆栈信息我们来调整下,调整后如下

package com.rui.tiger.auth.core.authentication;

import com.alibaba.fastjson.JSON;
import com.rui.tiger.auth.core.model.enums.LoginTypeEnum;
import com.rui.tiger.auth.core.properties.SecurityProperties;
import com.rui.tiger.auth.core.support.SimpleResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 认证失败处理器
 * @author CaiRui
 * @date 2018-12-6 12:40
 */
@Component("tigerAuthenticationFailureHandler")
@Slf4j
public class TigerAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {

    @Autowired
    private SecurityProperties securityProperties;

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        log.info("登录失败");
        if (LoginTypeEnum.JSON.equals(securityProperties.getBrowser().getLoginType())) {
            response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write(JSON.toJSONString(new SimpleResponse(exception.getMessage())));
        } else {
            // 如果用户配置为跳转,则跳到Spring Boot默认的错误页面
            super.onAuthenticationFailure(request, response, exception);
        }

    }
}

这次我们试下一个错误的验证码登录看下

 

 ok 我们的自定义验证码生效,其它的情况可以自行调试 下面我们将对验证码进行重构

3.图片验证码重构

  1. 验证码生成的基本参数可以配置
  2. 验证码拦截的接口可以配置
  3. 验证码的生成逻辑可以配置

3.1验证码生成的基本参数可以配置

图片验证码基本配置类

package com.rui.tiger.auth.core.properties;

/**
 * 图片验证码配置类,根据情况添加具体配置项
 *
 * @author CaiRui
 * @date 2018-12-11 9:02
 * @see com.google.code.kaptcha.Constants
 */
public class ImageCaptchaProperties {

	/**
	 * 验证码长度
	 */
	private int size = 4;

	/**
	 * 宽度
	 */
	private int width;
	/**
	 * 高度
	 */
	private int height;
	/**
	 * 失效秒数
	 */
	private int expireAfterSecondes = 3 * 60;//默认三分钟
	/**
	 * 验证码拦截的路径 多个路径以,(逗号)进行分割
	 */
	private String interceptImageUrl;



	public int getSize() {
		return size;
	}

	public void setSize(int size) {
		this.size = size;
	}

	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 getExpireAfterSecondes() {
		return expireAfterSecondes;
	}

	public void setExpireAfterSecondes(int expireAfterSecondes) {
		this.expireAfterSecondes = expireAfterSecondes;
	}

	public String getInterceptImageUrl() {
		return interceptImageUrl;
	}

	public void setInterceptImageUrl(String interceptImageUrl) {
		this.interceptImageUrl = interceptImageUrl;
	}

}

 加入到总配置类中

package com.rui.tiger.auth.core.properties;

import org.springframework.boot.context.properties.ConfigurationProperties;

/**
 * 权限配置文件父类(注意这里不用lombok 会读取不到)
 * 这里会有很多权限配置子模块
 *
 * @author CaiRui
 * @date 2018-12-6 8:41
 */

@ConfigurationProperties(value = "tiger.auth", ignoreInvalidFields = true)
public class SecurityProperties {

	/**
	 * 浏览器配置类
	 */
	private BrowserProperties browser = new BrowserProperties();
	/**
	 * 图片验证码配置类
	 */
	private ImageCaptchaProperties imageCaptcha = new ImageCaptchaProperties();


	public BrowserProperties getBrowser() {
		return browser;
	}

	public void setBrowser(BrowserProperties browser) {
		this.browser = browser;
	}

	public ImageCaptchaProperties getImageCaptcha() {
		return imageCaptcha;
	}

	public void setImageCaptcha(ImageCaptchaProperties imageCaptcha) {
		this.imageCaptcha = imageCaptcha;
	}
}

验证码生成参数从配置中获取

package com.rui.tiger.auth.core.config;

import com.google.code.kaptcha.Constants;
import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.google.code.kaptcha.util.Config;
import com.rui.tiger.auth.core.properties.SecurityProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Properties;

/**
 * 验证码生成配置类
 *
 * @author CaiRui
 * @date 2018-12-10 12:09
 * @see Constants
 */
@Configuration
public class CaptchaGenerateConfig {

	@Autowired
	private SecurityProperties securityProperties;//验证码配置获取

	// 参见 https://blog.csdn.net/larger5/article/details/79522105  DefaultKaptcha配置生成
	@Bean
	public DefaultKaptcha producer() {
		Properties properties = new Properties();
		properties.put("kaptcha.border", "no");
		//常量配置Constants和直接字符串配置都可以
		properties.put(Constants.KAPTCHA_TEXTPRODUCER_FONT_COLOR, "black");
		//验证码长度 put 不生效  注意
		//properties.put("kaptcha.textproducer.char.length", securityProperties.getImageCaptcha().getSize());
		properties.setProperty("kaptcha.textproducer.char.length", String.valueOf(securityProperties.getImageCaptcha().getSize()));

		Config config = new Config(properties);
		DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
		defaultKaptcha.setConfig(config);
		return defaultKaptcha;
	}
}

3.2 验证码拦截的接口可以配置

过滤器加入拦截的url匹配,对原来的进行重构

package com.rui.tiger.auth.core.captcha;

import com.rui.tiger.auth.core.properties.SecurityProperties;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
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;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 图片验证码过滤器
 * OncePerRequestFilter 过滤器只会调用一次
 *
 * @author CaiRui
 * @date 2018-12-10 12:23
 */
@Setter
@Getter
@Slf4j
public class CaptchaFilter extends OncePerRequestFilter implements InitializingBean {

	//一般在配置类中进行注入

	private AuthenticationFailureHandler failureHandler;

	private SecurityProperties securityProperties;

	/**
	 * 验证码拦截的路径
	 */
	private Set<String> interceptUrlSet = new HashSet<>();

	//session工具类
	private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
	//路径匹配工具类
	private AntPathMatcher antPathMatcher = new AntPathMatcher();

	/**
	 * @throws ServletException
	 */

	@Override
	public void afterPropertiesSet() throws ServletException {
		super.afterPropertiesSet();
		//其它配置的需要验证码验证的路径
		String configInterceptUrl = securityProperties.getImageCaptcha().getInterceptImageUrl();
		if (StringUtils.isNotBlank(configInterceptUrl)) {
			String[] configInterceptUrlArray = StringUtils.split(configInterceptUrl, ",");
			interceptUrlSet = Stream.of(configInterceptUrlArray).collect(Collectors.toSet());
		}
		//登录请求验证
		interceptUrlSet.add("/authentication/form");
	}

	@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

		log.info("验证码验证请求路径:[{}]", request.getRequestURI());
		boolean action = false;// 默认不放行
		for (String url : interceptUrlSet) {
			if (antPathMatcher.match(url, request.getRequestURI())) {
				action = true;
			}
		}
		if (action) {
			try {
				validate(request);
			} catch (CaptchaException captchaException) {
				//失败调用我们的自定义失败处理器
				failureHandler.onAuthenticationFailure(request, response, captchaException);
				//后续流程终止
				return;
			}

		}
		//后续过滤器继续执行
		filterChain.doFilter(request, response);
	}

	/**
	 * 图片验证码校验
	 *
	 * @param request
	 */
	private void validate(HttpServletRequest request) throws ServletRequestBindingException {
		// 拿到之前存储的imageCode信息
		ServletWebRequest swr = new ServletWebRequest(request);
		ImageCaptchaVo imageCodeInSession = (ImageCaptchaVo) sessionStrategy.getAttribute(swr, CaptchaController.IMAGE_CAPTCHA_SESSION_KEY);
		String codeInRequest = ServletRequestUtils.getStringParameter(request, "imageCode");

		if (StringUtils.isBlank(codeInRequest)) {
			throw new CaptchaException("验证码的值不能为空");
		}
		if (imageCodeInSession == null) {
			throw new CaptchaException("验证码不存在");
		}
		if (imageCodeInSession.isExpried()) {
			sessionStrategy.removeAttribute(swr, CaptchaController.IMAGE_CAPTCHA_SESSION_KEY);
			throw new CaptchaException("验证码已过期");
		}
		if (!StringUtils.equals(imageCodeInSession.getCode(), codeInRequest)) {
			throw new CaptchaException("验证码不匹配");
		}
		//验证通过 移除缓存
		sessionStrategy.removeAttribute(swr, CaptchaController.IMAGE_CAPTCHA_SESSION_KEY);
	}
}

 配置类中加入调整后的过滤器相关配置

package com.rui.tiger.auth.browser.config;

import com.rui.tiger.auth.core.authentication.TigerAuthenticationFailureHandler;
import com.rui.tiger.auth.core.authentication.TigerAuthenticationSuccessHandler;
import com.rui.tiger.auth.core.captcha.CaptchaFilter;
import com.rui.tiger.auth.core.properties.SecurityProperties;
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.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

/**
 * 浏览器security配置类
 *
 * @author CaiRui
 * @date 2018-12-4 8:41
 */
@Configuration
public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {

	@Autowired
	private SecurityProperties securityProperties;
	@Autowired
	private TigerAuthenticationFailureHandler tigerAuthenticationFailureHandler;
	@Autowired
	private TigerAuthenticationSuccessHandler tigerAuthenticationSuccessHandler;

	/**
	 * 密码加密解密
	 *
	 * @return
	 */
	@Bean
	public PasswordEncoder passwordEncoder() {
		return new BCryptPasswordEncoder();
	}

	@Override
	protected void configure(HttpSecurity http) throws Exception {
		//加入图片验证码过滤器
		CaptchaFilter captchaFilter = new CaptchaFilter();
		captchaFilter.setFailureHandler(tigerAuthenticationFailureHandler);
		captchaFilter.setSecurityProperties(securityProperties);
		captchaFilter.afterPropertiesSet();

		//图片验证码放在认证之前
		http.addFilterBefore(captchaFilter, UsernamePasswordAuthenticationFilter.class)
				.formLogin()
				.loginPage("/authentication/require")//自定义登录请求
				.loginProcessingUrl("/authentication/form")//自定义登录表单请求
				.successHandler(tigerAuthenticationSuccessHandler)
				.failureHandler(tigerAuthenticationFailureHandler)
				.and()
				.authorizeRequests()
				.antMatchers(securityProperties.getBrowser().getLoginPage(),
						"/authentication/require", "/captcha/image")//此路径放行 否则会陷入死循环
				.permitAll()
				.anyRequest()
				.authenticated()
				.and()
				.csrf().disable()//跨域关闭
		;
	}

}

测试配置文件调整如下

#数据源
spring:
  datasource:
    driverClassName: com.mysql.jdbc.Driver
    url: jdbc:mysql://my.yunout.com:3306/tiger_study?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8
    username: root
    password: root
    # 配置Druid连接池
    type: com.alibaba.druid.pool.DruidDataSource
  session:
    store-type: none

# Tomcat
server:
  port: 8070
  connection-timeout: 5000ms

#自定义权限配置
tiger:
  auth:
     browser:
       #loginPage: /demo-login.html # 这里可以配置成自己的非标准登录界面
        loginType: JSON
     imageCaptcha:
        interceptImageUrl: /user/*,/pay/confirm # 这些路径验证码也要拦截校验

ok 我们来测试下 是否可用 

3.3 验证码的生成逻辑可以配置

主要是利用@Conditional系列注解,可参看@Conditional系列注解

package com.rui.tiger.auth.core.config;

import com.google.code.kaptcha.Producer;
import com.rui.tiger.auth.core.captcha.CaptchaGenerate;
import com.rui.tiger.auth.core.captcha.ImageCaptchaGenerate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 验证码Bean生成配置类
 *
 * @author CaiRui
 * @date 2018-12-12 8:41
 */
@Configuration
public class CaptchaGenerateBeanConfig {

	@Bean
	// spring 容器中如果存在imageCaptchaGenerate的bean就不会再初始化该bean了
	//可参见:https://www.cnblogs.com/yixianyixian/p/7346894.html 这篇博文
	@ConditionalOnMissingBean(name = "imageCaptchaGenerate")
	public CaptchaGenerate imageCaptchaGenerate() {
		ImageCaptchaGenerate imageCaptchaGenerate = new ImageCaptchaGenerate();
		return imageCaptchaGenerate;
	}

}

原来的service去掉注解

package com.rui.tiger.auth.core.captcha;

import com.google.code.kaptcha.Producer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.awt.image.BufferedImage;

/**
 * 图片验证码生成接口
 *
 * @author CaiRui
 * @date 2018-12-10 12:07
 */
//@Service("imageCaptchaGenerate")

public class ImageCaptchaGenerate implements CaptchaGenerate {

	@Autowired
	private Producer producer;//config bean中配置

	@Override
	public ImageCaptchaVo generate() {
		String code = producer.createText();
		BufferedImage bufferedImage = producer.createImage(code);
		return new ImageCaptchaVo(bufferedImage, code, 60 * 5);//5分钟过期
	}
}

demo项目中写个测试类看是否生效,这里我们不生成,只是打印下日志

package com.rui.tiger.auth.demo.service;

import com.rui.tiger.auth.core.captcha.CaptchaGenerate;
import com.rui.tiger.auth.core.captcha.ImageCaptchaVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

/**
 * @author CaiRui
 * @date 2018-12-12 9:05
 */
@Component("imageCaptchaGenerate")
@Slf4j
public class DemoImageCaptchaGenerateTest implements CaptchaGenerate {

	/**
	 * 测试覆盖 测试通过将其@Component去掉 保证正常流程执行
	 *
	 * @return
	 * @see com.rui.tiger.auth.core.config.CaptchaGenerateBeanConfig
	 */
	@Override
	public ImageCaptchaVo generate() {
		log.info("自定义验证码实现 覆盖原始的生成测试");
		return null;
	}
}

 

 


版权声明:本文为ahcr1026212原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。