考虑到有多种验证机制(例如:常见的图片验证码,手机短信验证码,邮箱验证码)
所以在项目中对验证码进行代码重构,使之更加的具有可扩展性,低耦合性,此项目基于springboot
1.首先Controller层
@RestController
public class ValidateCodeController {
@Autowired
private ValidateCodeProcessorHolder holder;
@GetMapping(SecurityConstants.DEFAULT_VALIDATE_CODE_URL_PREFIX+"/{type}")
public void createCode(HttpServletRequest request, HttpServletResponse response, @PathVariable String type) throws Exception {
holder.findValidateCodeProcessor(type).create(request,response);
}
}
前端发起类似 /code/image这样的请求,将验证码类型获取到,从hold中找到哪个验证码处理器来进行处理
2.验证码管家


package club.wenfan.youtube.validate;
import club.wenfan.youtube.validate.exception.ValidateCodeException;
import club.wenfan.youtube.validate.processor.ValidateCodeProcessor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
* @author:wenfan
* @description:
* @data: 2019/1/22 9:31
*/
@Component
public class ValidateCodeProcessorHolder {
@Autowired
private Map<String,ValidateCodeProcessor> validateCodeProcessors;
private Logger log = LoggerFactory.getLogger(getClass());
public ValidateCodeProcessor findValidateCodeProcessor(ValidateCodeType type){
return findValidateCodeProcessor(type.toString().toLowerCase());
}
public ValidateCodeProcessor findValidateCodeProcessor(String type){
String name=type.toLowerCase() + ValidateCodeProcessor.class.getSimpleName();
ValidateCodeProcessor processor=validateCodeProcessors.get(name); //通过类型查找出用那个验证码处理器
log.info("验证码处理器"+name);
if(processor == null){
throw new ValidateCodeException("验证码处理器"+name+"不存在");
}
return processor;
}
}
ValidateCodeProcessorHolder类
特别说明一下
@Autowired
private Map<String,ValidateCodeProcessor> validateCodeProcessors;采用这样的Map Bean注入方式,注入时将所有Bean的名字和类型作为Map注入进来
然后选择时通过Bean的名字来确定用哪个验证码处理器来完成。
3.ValidateCodeProcessor接口
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author:wenfan
* @description:
* @data: 2019/1/21 12:03
*/
public interface ValidateCodeProcessor {
/**
* 验证码放入session时的前缀
* @author wenfan
* @date
* @param
* @return
*/
String SESSION_KEY_PREFIX="SESSION_KEY_FOR_CODE_";
/**
* 创建校验码
* @author wenfan
*/
void create(HttpServletRequest request, HttpServletResponse response) throws Exception;
void validate(HttpServletRequest request,HttpServletResponse response);
}
4.将验证码处理器的生成、保存、验证、验证抽象出来,单独将发送写成一个抽象方法,用具体的验证码处理来实现此方法


import club.wenfan.youtube.validate.ValidateCodeType;
import club.wenfan.youtube.validate.code.ValidateCode;
import club.wenfan.youtube.validate.code.ValidateCodeGenerator;
import club.wenfan.youtube.validate.exception.ValidateCodeException;
import club.wenfan.youtube.validate.processor.ValidateCodeProcessor;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.ServletRequestUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
/**
*
* @author wenfan
* @date
* @param
* @return
*/
public abstract class AbstractValidateCodeProcessor<C extends ValidateCode> implements ValidateCodeProcessor {
@Autowired
private Map<String, ValidateCodeGenerator> validateCodeGenerators;
/**
*
* @author wenfan
* @date
* @param
* @return
*/
@Override
public void create(HttpServletRequest request, HttpServletResponse response) throws Exception {
C validateCode = generate(request);
System.out.println(request.getRequestURI());
System.out.println(validateCode.getCode());
save(request, validateCode);
send(request,response, validateCode);
}
/**
* 生成校验码
*
* @param request
* @return
*/
@SuppressWarnings("unchecked")
private C generate(HttpServletRequest request) {
String type = getValidateCodeType(request).toString().toLowerCase();
String generatorName = type + ValidateCodeGenerator.class.getSimpleName();
ValidateCodeGenerator validateCodeGenerator = validateCodeGenerators.get(generatorName);
if (validateCodeGenerator == null) {
throw new ValidateCodeException("验证码生成器" + generatorName + "不存在");
}
return (C) validateCodeGenerator.CreateCode(request);
}
/**
* 保存校验码
*
* @param request
* @param validateCode
*/
private void save(HttpServletRequest request, C validateCode) {
ValidateCode code = new ValidateCode(validateCode.getCode(),validateCode.getExpireTime());
System.out.println(getSessionKey(request));
request.getSession(true).setAttribute(getSessionKey(request), code);
System.out.println(request.getSession().getAttribute(getSessionKey(request)));
}
/**
* 构建验证码放入session时的key
*
* @param request
* @return
*/
private String getSessionKey(HttpServletRequest request) {
return SESSION_KEY_PREFIX + getValidateCodeType(request).toString().toUpperCase();
}
/**
* 发送校验码,由子类实现
*
* @param request
* @param validateCode
* @throws Exception
*/
protected abstract void send(HttpServletRequest request,HttpServletResponse response, C validateCode) throws Exception;
/**
* 根据请求的url获取校验码的类型
*
* @param request
* @return
*/
private ValidateCodeType getValidateCodeType(HttpServletRequest request) {
String type = StringUtils.substringBefore(getClass().getSimpleName(), "CodeProcessor");
return ValidateCodeType.valueOf(type.toUpperCase());
}
@SuppressWarnings("unchecked")
@Override
public void validate(HttpServletRequest request,HttpServletResponse response) {
ValidateCodeType processorType = getValidateCodeType(request);
String sessionKey = getSessionKey(request);
System.out.println("sessionKey="+sessionKey);
C codeInSession = (C) request.getSession(false).getAttribute(sessionKey);
System.out.println(codeInSession==null?"codeinsession为null":"codeinsession不为null");
String codeInRequest;
try {
codeInRequest = ServletRequestUtils.getStringParameter(request,
processorType.getParamNameOnValidate());
} catch (ServletRequestBindingException e) {
throw new ValidateCodeException("获取验证码的值失败");
}
if (StringUtils.isBlank(codeInRequest)) {
throw new ValidateCodeException(processorType + "验证码的值不能为空");
}
if (codeInSession == null) {
throw new ValidateCodeException(processorType + "验证码不存在");
}
if (codeInSession.isExpired()) {
request.getSession().removeAttribute(sessionKey);
throw new ValidateCodeException(processorType + "验证码已过期");
}
if (!StringUtils.equals(codeInSession.getCode(), codeInRequest)) {
throw new ValidateCodeException(processorType + "验证码不匹配");
}
request.getSession().removeAttribute(sessionKey);
}
}
AbstractValidateCodeProcessor
5.具体的验证码生成器只有发送方法,这里只贴了图片验证码处理器
@Component("imageValidateCodeProcessor")
public class ImageCodeProcessor extends AbstractValidateCodeProcessor<ImgCode> {
@Override
protected void send(HttpServletRequest request, HttpServletResponse response, ImgCode imgCode) throws Exception {
ImageIO.write(imgCode.getImage(),"JPEG",response.getOutputStream());
}
}
6.在处理器抽象法中也运用方法Map Bean 的方式来选择验证码生成器的类型
public interface ValidateCodeGenerator {
ValidateCode CreateCode(HttpServletRequest request);
}
7.具体的验证码生成器


import club.wenfan.youtube.properties.SecurityProperties;
import club.wenfan.youtube.validate.code.ImgCode;
import club.wenfan.youtube.validate.code.ValidateCodeGenerator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.ServletRequestUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.Random;
/**
* @author:wenfan
* @description:
* @data: 2019/1/1 18:33
*/
@Component("imageValidateCodeGenerator")
public class ImgCodeGenerator implements ValidateCodeGenerator {
@Autowired
private SecurityProperties securityProperties;
private Logger log = LoggerFactory.getLogger(getClass());
@Override
public ImgCode CreateCode(HttpServletRequest request) {
//可以在请求中加入 width/height get参数 当没有参数时从用户的自定义的配置文件中读取
int width =ServletRequestUtils.getIntParameter(request,"width",securityProperties.getCode().getImg().getWidth());// 定义图片的width
int height = ServletRequestUtils.getIntParameter(request," height",securityProperties.getCode().getImg().getHeight());// 定义图片的height
int codeCount =securityProperties.getCode().getImg().getCodeCount();// 定义图片上显示验证码的个数
int expiredTime = securityProperties.getCode().getImg(). getExpiredTime();
int xx = 18;
int fontHeight = 20;
int codeY = 27;
char[] codeSequence = { '0','1', '2', '3', '4','5', '6', '7', '8', '9' };
// 定义图像buffer
BufferedImage buffImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics gd = buffImg.getGraphics();
// 创建一个随机数生成器类
Random random = new Random();
// 将图像填充为白色
gd.setColor(Color.WHITE);
gd.fillRect(0, 0, width, height);
// 创建字体,字体的大小应该根据图片的高度来定。
Font font = new Font("Fixedsys", Font.BOLD, fontHeight);
// 设置字体。
gd.setFont(font);
// 画边框。
gd.setColor(Color.BLACK);
gd.drawRect(0, 0, width - 1, height - 1);
// 随机产生40条干扰线,使图象中的认证码不易被其它程序探测到。
gd.setColor(Color.BLACK);
for (int i = 0; i < 30; i++) {
int x = random.nextInt(width);
int y = random.nextInt(height);
int xl = random.nextInt(12);
int yl = random.nextInt(12);
gd.drawLine(x, y, x + xl, y + yl);
}
// randomCode用于保存随机产生的验证码,以便用户登录后进行验证。
StringBuffer randomCode = new StringBuffer();
int red = 0, green = 0, blue = 0;
// 随机产生codeCount数字的验证码。
for (int i = 0; i <codeCount; i++) {
// 得到随机产生的验证码数字。
String code = String.valueOf(codeSequence[random.nextInt(10)]);
// 产生随机的颜色分量来构造颜色值,这样输出的每位数字的颜色值都将不同。
red = random.nextInt(255);
green = random.nextInt(255);
blue = random.nextInt(255);
// 用随机产生的颜色将验证码绘制到图像中。
gd.setColor(new Color(red, green, blue));
gd.drawString(code, (i + 1) * xx, codeY);
// 将产生的四个随机数组合在一起。
randomCode.append(code);
}
log.info("产生验证码"+randomCode.toString());
return new ImgCode(buffImg,randomCode.toString(),expiredTime);
}
public SecurityProperties getSecurityProperties() {
return securityProperties;
}
public void setSecurityProperties(SecurityProperties securityProperties) {
this.securityProperties = securityProperties;
}
}
View Code
8.如果想更换系统中默认的验证码处理器可以采用一种可扩展的bean注入方式
@Configuration
public class ValidateCodeBeanConfig {
@Autowired
private SecurityProperties securityProperties;
/**
* 当容器中没有imageValidateCodeGenerator 这个Bean的时候,会主动配置下面的默认Bean
* 以增量的形式实现变化不
*
* @author wenfan
* @date
* @param
* @return
*/
@Bean
@ConditionalOnMissingBean(name = "imageValidateCodeGenerator")
public ValidateCodeGenerator imgCodeGenerator(){
ImgCodeGenerator codeGenerator=new ImgCodeGenerator();
codeGenerator.setSecurityProperties(securityProperties);
return codeGenerator;
}
/**
* class形式和 name的方式相同
* @author wenfan
* @date
* @param
* @return
*/
@Bean
@ConditionalOnMissingBean(SmsCodeSender.class)
public SmsCodeSender smsCodeSender(){
return new SmsCodeSenderImpl();
}
}
如果不想用默认验证码生成器,之需要注册bean,bean名字为imageValidateCodeGenerator
9.验证码过滤
写一个过滤器继承OncePerRequestFilter
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
response.setContentType("application/json;charset=utf-8");
ValidateCodeType type=getValidateCodeType(request);
if(type !=null){
("校验请求(" + request.getRequestURI() + ")中的验证码,验证码类型" + type);
try {
validateCodeProcessorHolder.findValidateCodeProcessor(type).validate(request,response);
("验证码通过");
} catch (ValidateCodeException e) {
authenticationFailureHandler.onAuthenticationFailure(request,response,e);
return;
}
}
filterChain.doFilter(request,response);
}
发起请求时,验证码处理器管家来决定哪一个处理器来处理
10.将过滤器添加到配置中
@Component("validateCodeSecurityConfig")
public class ValidateCodeSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain,HttpSecurity> {
@Autowired
private Filter vaildateCodeFilter;
@Override
public void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(vaildateCodeFilter,AbstractPreAuthenticatedProcessingFilter.class);
}
}
感谢阅读
















