在很多登录功能中,需要用到图形验证码,所以简单的实现下面的图形验证码的功能
图形验证码
1.获取图形验证码
图形验证码的获取实现分为三步
- 1.生成随机的验证码以及验证码的图片信息
- 2.将生成的图形验证码存入Session中
- 3.将生成的验证码图片写入接口的响应中
@GetMapping("/code/image")
public void createCode(HttpServletRequest request, HttpServletResponse response) throws IOException {
//生成随机验证码
ImageCode imageCode = imageCodeGenerator.generateImageCode(request);
//将随机数存入session
sessionStrategy.setAttribute(new ServletWebRequest(request),IMAGE_CODE_KEY,imageCode);
//将生成的图片写到接口的响应中
ImageIO.write(imageCode.getImage(),"JPEG",response.getOutputStream());
}
1.1 生成随机验证码
- 定义生成验证码的接口
public interface ImageCodeGenerator {
/**
* 生成图形验证码
* @param request
* @return
*/
ImageCode generateImageCode(HttpServletRequest request);
}
- 实现图形验证码的生成
public class DefaultImageGenerator implements ImageCodeGenerator {
private SecurityProperties securityProperties;
@Override
public ImageCode generateImageCode(HttpServletRequest request) {
int width = ServletRequestUtils.getIntParameter(request,"width",securityProperties.getValidate().getImage().getWidth());
int height = ServletRequestUtils.getIntParameter(request,"height",securityProperties.getValidate().getImage().getHeight());
BufferedImage image = new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);
Graphics g = image.getGraphics();
Random random = new Random();
g.setColor(getRandColor(200,250));
g.fillRect(0,0,width,height);
g.setFont(new Font("Times New Roman",Font.ITALIC,20));
g.setColor(getRandColor(160,200));
for(int i=0; i<155;i++){
int x = random.nextInt(width);
int y = random.nextInt(height);
int x1 = random.nextInt(12);
int y1 = random.nextInt(12);
g.drawLine(x,y,x+x1,y+y1);
}
String sRand = "";
for(int i=0; i<securityProperties.getValidate().getImage().getLength();i++){
String rand = String.valueOf(random.nextInt(10));
sRand += rand;
g.setColor(new Color(20+random.nextInt(110),20+random.nextInt(110),20+random.nextInt(110)));
g.drawString(rand,13*i+6,16);
}
g.dispose();
return new ImageCode(image,sRand,securityProperties.getValidate().getImage().getExpiredIn());
}
private Color getRandColor(int fc, int bc) {
Random random = new Random();
if(fc>255){
fc = 255;
}
if(bc>255){
bc = 255;
}
int r = fc+random.nextInt(bc-fc);
int g = fc+random.nextInt(bc-fc);
int b = fc+random.nextInt(bc-fc);
return new Color(r,g,b);
}
public SecurityProperties getSecurityProperties() {
return securityProperties;
}
public void setSecurityProperties(SecurityProperties securityProperties) {
this.securityProperties = securityProperties;
}
}
- 其中用到了
securityProperties
是对相关的属性的定义
private int width = 130;
private int height = 20;
private int length = 4;
private int expiredIn = 60;
private String urls;
主要是图片的宽度,高度,验证码过期时间,验证码长度,以及过滤的urls,这样我们就生成了图形验证码。
其实图形验证码和短信验证码的生成逻辑非常的相似,再后面的短信验证码生成再做一个抽象
2.验证图形验证码
2.1 创建验证图形验证码的过滤器
/**
* @Auther: guaidan
* @Date: 2018/10/12 13:41
* @Description:
*/
public class ValidateFilter extends OncePerRequestFilter implements InitializingBean {
private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
private AuthenticationFailureHandler authenticationFailureHandler;
private SecurityProperties securityProperties;
private Set<String> urls = new HashSet<>();
private AntPathMatcher pathMatcher = new AntPathMatcher();
public ValidateFilter(AuthenticationFailureHandler authenticationFailureHandler,SecurityProperties securityProperties) throws ServletException {
this.authenticationFailureHandler = authenticationFailureHandler;
this.securityProperties = securityProperties;
}
@Override
public void afterPropertiesSet() throws ServletException{
super.afterPropertiesSet();
String[] configUrls = StringUtils.splitByWholeSeparatorPreserveAllTokens(securityProperties.getValidate().getImage().getUrls(),",");
for (String configUrl:configUrls){
urls.add(configUrl);
}
urls.add("/authentication/form");
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
boolean action = false;
for(String url:urls){
if(pathMatcher.match(url,request.getRequestURI())){
action = true;
}
}
if(action){
//对图形验证码进行验证
try{
validate(request);
}catch (ValidateImageCodeException e){
authenticationFailureHandler.onAuthenticationFailure(request,response,e);
return;
}
}
filterChain.doFilter(request,response);
}
private void validate(HttpServletRequest request) {
String imageCode = request.getParameter("imageCode");
ServletWebRequest servletWebRequest = new ServletWebRequest(request);
ImageCode sessionImageCode = (ImageCode) sessionStrategy.getAttribute(servletWebRequest, ValidateCodeController.IMAGE_CODE_KEY);
if(StringUtils.isBlank(imageCode)){
throw new ValidateImageCodeException("验证码为空");
}
if(null==sessionImageCode){
throw new ValidateImageCodeException("验证码不存在");
}
if(LocalDateTime.now().isAfter(sessionImageCode.getExpiredTime())){
sessionStrategy.removeAttribute(servletWebRequest, ValidateCodeController.IMAGE_CODE_KEY);
throw new ValidateImageCodeException("验证码已过期");
}
if(!StringUtils.equalsIgnoreCase(imageCode,sessionImageCode.getCode())){
sessionStrategy.removeAttribute(servletWebRequest, ValidateCodeController.IMAGE_CODE_KEY);
throw new ValidateImageCodeException("验证码不匹配");
}
sessionStrategy.removeAttribute(servletWebRequest, ValidateCodeController.IMAGE_CODE_KEY);
}
}
- 第一步,初始化的时候获取到所有需要过滤的url
- 第二部,对于需要过滤的url做正则匹配,因为我们配置可能是用正则表达式的形式
- 第三部, 获取到session中存储的图片验证码,然后和用户输入的图形验证码进行比较,比对失败,对相应的错误利用AuthenticationFailureHandler 返回回去。
网友评论