图片验证码重构
- 验证码基本参数可配置
- 验证码校验拦截的接口可配置
- 验证码的生成逻辑可配
验证码基本参数配置
三级覆盖:在最上面的会覆盖下级的配置
↓ 请求级配置 :配置值在调用接口的时候传递
↓ 应用级配置 :配置写在security-demo项目中
↓ 默认配置 :配置值写在security-core项目中
图形验证码配置类
package cn.mrcode.imooc.springsecurity.securitycore.properties;
/**
* 图形验证码
* @author zhuqiang
* @version 1.0.1 2018/8/4 10:03
* @date 2018/8/4 10:03
* @since 1.0
*/
public class ImageCodeProperties {
private int width = 67;
private int height = 23;
private int length = 4; // 验证码长度
private int expireIn = 60; // 过期时间
验证码配置类
package cn.mrcode.imooc.springsecurity.securitycore.properties;
/**
* 用来封装验证码相关的配置
* @author zhuqiang
* @version 1.0.1 2018/8/4 10:06
* @date 2018/8/4 10:06
* @since 1.0
*/
public class ValidateCodeProperties {
private ImageCodeProperties image = new ImageCodeProperties();
// 后面还会新增短信验证码的配置
加入总配置类中
@ConfigurationProperties(prefix = "imooc.security")
public class SecurityProperties {
/** imooc.security.browser 路径下的配置会被映射到该配置类中 */
private BrowserProperties browser = new BrowserProperties();
private ValidateCodeProperties code = new ValidateCodeProperties();
修改处理逻辑处
cn.mrcode.imooc.springsecurity.securitycore.validate.code.ValidateCodeController
private ImageCode createImageCode(HttpServletRequest request) throws IOException {
ImageCodeProperties imageProperties = securityProperties.getCode().getImage();
// 先从请求中获取,然后从配置中获取
// 如果配置中的没有被覆盖则是默认配置
int width = ServletRequestUtils.getIntParameter(request, "width", imageProperties.getWidth());
int height = ServletRequestUtils.getIntParameter(request, "height", imageProperties.getHeight());
int length = ServletRequestUtils.getIntParameter(request, "length", imageProperties.getLength());
int expireIn = ServletRequestUtils.getIntParameter(request, "expireIn", imageProperties.getExpireIn());
String code = RandomStringUtils.randomNumeric(length);
BufferedImage image = createImageCode(width, height, code);
return new ImageCode(image, code, expireIn);
}
验证码校验拦截的接口可配置
实现思路:
- 提供url拦截地址配置属性
- 过滤器中获取配置的属性,并且循环匹配
增加url配置属性
public class ImageCodeProperties {
private int width = 67;
private int height = 23;
private int length = 4; // 验证码长度
private int expireIn = 60; // 过期时间
private String url; // 要验证的接口url路径,逗号隔开
过滤器中对目标url进行匹配逻辑
public class ValidateCodeFilter extends OncePerRequestFilter implements InitializingBean {
// 在初始化本类的地方进行注入
// 一般在配置security http的地方进行添加过滤器
private AuthenticationFailureHandler failureHandler;
private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
// 由初始化的地方传递进来
private SecurityProperties securityProperties;
// 存储所有需要拦截的url
private Set<String> urls;
private AntPathMatcher pathMatcher = new AntPathMatcher();
/**
* org.springframework.beans.factory.InitializingBean 保证在其他属性都设置完成后,有beanFactory调用
* 但是在这里目前还是需要初始化处调用该方法
* @throws ServletException
*/
@Override
public void afterPropertiesSet() throws ServletException {
super.afterPropertiesSet();
String url = securityProperties.getCode().getImage().getUrl();
String[] configUrl = StringUtils.split(url, ",");
urls = Stream.of(configUrl).collect(Collectors.toSet());
urls.add("/authentication/form"); // 登录请求
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
// 为登录请求,并且为post请求
boolean action = false;
for (String url : urls) {
// org.springframework.util.AntPathMatcher 能匹配spring中的url模式
// 支持通配符路径那种
if (pathMatcher.match(url, request.getRequestURI())) {
action = true;
}
}
if (action) {
try {
validate(request);
} catch (ValidateCodeException e) {
failureHandler.onAuthenticationFailure(request, response, e);
return;
}
}
filterChain.doFilter(request, response);
}
原配置中心进行属性注入
cn.mrcode.imooc.springsecurity.securitybrowser.BrowserSecurityConfig
// 有三个configure的方法,这里使用http参数的
@Override
protected void configure(HttpSecurity http) throws Exception {
// 最简单的修改默认配置的方法
// 在v5+中,该配置(表单登录)应该是默认配置了
// basic登录(也就是弹框登录的)应该是v5-的版本默认
ValidateCodeFilter validateCodeFilter = new ValidateCodeFilter();
validateCodeFilter.setFailureHandler(myAuthenticationFailureHandler);
validateCodeFilter.setSecurityProperties(securityProperties); // 注入配置属性类
validateCodeFilter.afterPropertiesSet(); // 初始化url配置
测试:security-demo/application.yml
imooc:
security:
browser:
# loginPage: /demo-signIn.html
# loginType: REDIRECT
loginType: JSON
code:
image:
width: 100
height: 50
url: /order,/user/* # 对订单和所有user路径进行验证码拦截 */
验证码的生成逻辑可配
思路:逻辑可配,就是抽象成接口,然后由客户端提供
这里的思路很强大,让我学习到了 spring中的默认配置是怎么实现的
提供一个生成图片信息的接口
package cn.mrcode.imooc.springsecurity.securitycore.validate.code;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
public interface ValidateCodeGenerate {
ImageCode generate(HttpServletRequest request) throws IOException;
}
实现默认图片生成接口类
cn.mrcode.imooc.springsecurity.securitycore.validate.code.ImageCodeGenerate
public class ImageCodeGenerate implements ValidateCodeGenerate {
private ImageCodeProperties imageProperties;
public ImageCodeGenerate(ImageCodeProperties imageProperties) {
this.imageProperties = imageProperties;
}
@Override
public ImageCode generate(HttpServletRequest request) throws IOException {
return createImageCode(request);
}
public ImageCode createImageCode(HttpServletRequest request) throws IOException {
int width = ServletRequestUtils.getIntParameter(request, "width", imageProperties.getWidth());
int height = ServletRequestUtils.getIntParameter(request, "height", imageProperties.getHeight());
int length = ServletRequestUtils.getIntParameter(request, "length", imageProperties.getLength());
int expireIn = ServletRequestUtils.getIntParameter(request, "expireIn", imageProperties.getExpireIn());
String code = RandomStringUtils.randomNumeric(length);
BufferedImage image = createImageCode(width, height, code);
return new ImageCode(image, code, expireIn);
}
...后面的就是具体的工具类代码 不贴了
增加配置类,初始化图片生成器实例;这个是重点!!
package cn.mrcode.imooc.springsecurity.securitycore.validate.code;
import cn.mrcode.imooc.springsecurity.securitycore.properties.SecurityProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ValidateCodeConfig {
@Autowired
private SecurityProperties securityProperties;
@Bean
// spring 容器中如果存在imageCodeGenerate的bean就不会再初始化该bean了
// 条件注解
@ConditionalOnMissingBean(name = "imageCodeGenerate")
public ValidateCodeGenerate imageCodeGenerate() {
ImageCodeGenerate imageCodeGenerate = new ImageCodeGenerate(securityProperties.getCode().getImage());
return imageCodeGenerate;
}
}
之前调用处修改成调用接口
public class ValidateCodeController {
public static final String SESSION_KEY = "SESSION_KEY_IMAGE_CODE";
private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
@Autowired
private SecurityProperties securityProperties;
@Autowired // 使用生成接口处理信息
private ValidateCodeGenerate validateCodeGenerate;
最后测试思路:
- 先保证重构后的代码功正常工作
- 在demo项目中 实现兵初始化一个ValidateCodeGenerate实现类,调试看看是否走进了我们自己的生成逻辑
小结
本章知识点
* SessionStrategy
* @ConditionalOnMissingBean(name = "imageCodeGenerate")
bean条件注解
* AntPathMatcher 路径工具类