4. Spring Boot + Spring Security 图形验证码

本文详细介绍了一套自定义图形验证码的生成与验证系统,包括验证码的尺寸、过期时间等参数的设置,以及如何在Spring框架下进行验证码的生成、存储与验证,实现了用户登录过程中的安全性增强。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

思路:需要用户自定义设置宽度、高度、长度、以及验证码过期时间
在这里插入图片描述

  1. ImageCodeProperties.java 用户自定义图片信息
package com.imooc.security.core.properties;
/**
 * URL 表示用户需要登陆验证的请求
 * @author cjj
 * @date 2018年10月29日
 * @email 729165621@qq.com
 * @blog blog.youkuaiyun.com/qq_29451823
 */
public class ImageCodeProperties {
	private int width = 67;
	private int height = 23;
	private int length = 4;
	private int exprieIn = 60;
	private String url;
	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 getLength() {
		return length;
	}
	public void setLength(int length) {
		this.length = length;
	}
	public int getExprieIn() {
		return exprieIn;
	}
	public void setExprieIn(int exprieIn) {
		this.exprieIn = exprieIn;
	}
	public String getUrl() {
		return url;
	}
	public void setUrl(String url) {
		this.url = url;
	}
	
}	

  1. ValidateCodeProperties.java 封装,因为后面还有一个短信验证码
package com.imooc.security.core.properties;

/**
 * 默认验证
 * @author cjj
 * @date 2018年10月15日
 * @email 729165621@qq.com
 * @blog blog.youkuaiyun.com/qq_29451823
 */
public class ValidateCodeProperties {
	
	private ImageCodeProperties image = new ImageCodeProperties();

	public ImageCodeProperties getImage() {
		return image;
	}

	public void setImage(ImageCodeProperties image) {
		this.image = image;
	}
	
	
}

  1. SecurityProperties.java 读取properties 文件
package com.imooc.security.core.properties;

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

/**
 * 读取所有以imooc.security开头的properties配置项
 * @author cjj
 * @date 2018年9月21日
 * @email 729165621@qq.com
 * @blog blog.youkuaiyun.com/qq_29451823
 */
@ConfigurationProperties(prefix = "imooc.security")
public class SecurityProperties {
	
	private BrowserProperties browser = new BrowserProperties();
	
	private ValidateCodeProperties validate = new ValidateCodeProperties();
	
	public ValidateCodeProperties getValidate() {
		return validate;
	}

	public void setValidate(ValidateCodeProperties validate) {
		this.validate = validate;
	}

	public BrowserProperties getBrowser() {
		return browser;
	}

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

  1. ValidateCodeGenerator.java 验证图片生成器接口
package com.imooc.security.core.validate.code;

import org.springframework.stereotype.Component;
import org.springframework.web.context.request.ServletWebRequest;

public interface ValidateCodeGenerator {
	
	ImageCode createImage(ServletWebRequest request);
}

  1. ImageCodeGenertor.java 实现类
package com.imooc.security.core.validate.code;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.util.Random;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.context.request.ServletWebRequest;

import com.imooc.security.core.properties.SecurityProperties;

public class ImageCodeGenertor implements ValidateCodeGenerator{

	private SecurityProperties securityProperties;

	 public ImageCode createImage(ServletWebRequest request) {
		 //获取用户设置的宽度
		int width = ServletRequestUtils.getIntParameter(request.getRequest(), "width",
				securityProperties.getValidate().getImage().getWidth());
		//获取用户设置的高度
		int height = ServletRequestUtils.getIntParameter(request.getRequest(), "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 xl = random.nextInt(12);
			int yl = random.nextInt(12);
			g.drawLine(x, y, x + xl, y + yl);
		}

		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().getExprieIn());

	}
	/**
	 * 生成的随机条纹码
	 * @param fc
	 * @param bc
	 * @return
	 */
	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;
	}
	
	
}

  1. ValidateCodeBeanConfig.java 生成spring javabean
package com.imooc.security.core.validate.code;

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;

import com.imooc.security.core.properties.SecurityProperties;

@Configuration
public class ValidateCodeBeanConfig {
	
	@Autowired
	private SecurityProperties securityProperties;
	
	@Bean
	@ConditionalOnMissingBean(name="imageCodeGenertor")//如果初始化spring容器的时候,里面有imageCodeGenerator则就不用加载下面的内容
	public ValidateCodeGenerator imageCodeGenertor() {
		ImageCodeGenertor codeGenertor = new ImageCodeGenertor();
		codeGenertor.setSecurityProperties(securityProperties);
		return codeGenertor;
	}
}

  1. imooc-signIn.html 前端页面
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<h1>标准登陆页面</h1>
	<form action="/authentication/from" 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="/code/image?width=200">
				</td>
			</tr>
			<tr>
				<td clospan="2">
					<input type="submit" value="登陆">
				</td>
			</tr>
		</table>
	</form>
</body>
</html>
  1. ValidateCodeController.java 控制类
package com.imooc.security.core.validate.code;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Random;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

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.ServletRequestUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.ServletWebRequest;

import com.imooc.security.core.properties.SecurityProperties;

@RestController
public class ValidateCodeController {
	
	protected static final String SESSION_KEY=  "SESSION_KEY_IMAGE_CODE";
	//在session 中添加一个sessionId
	private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
	
	@Autowired
	private ValidateCodeGenerator imageCodeGenertor;
	
	@GetMapping("/code/image")
	public void createImage(HttpServletRequest request,HttpServletResponse response) throws IOException {
		//根据请求生成图片验证码对象
		ImageCode imageCode = imageCodeGenertor.createImage(new ServletWebRequest(request));
		sessionStrategy.setAttribute(new ServletWebRequest(request), SESSION_KEY, imageCode);
		ImageIO.write(imageCode.getImage(), "JPEG", response.getOutputStream());
	}

	
	
}

  1. ValidateCodeFilter.java 过滤



package com.imooc.security.core.validate.code;

import java.io.IOException;
import java.util.HashSet;
import java.util.Set;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
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 com.imooc.security.core.properties.SecurityProperties;

/**
 * 实现InitializingBean接口,并实现初始化时要实现的方法afterPropertiesSet()
 * OncePerRequestFilter确保在一次请求只通过一次filter,而不需要重复执行。
 * @author cjj
 * @date 2018年10月15日
 * @email 729165621@qq.com
 * @blog blog.youkuaiyun.com/qq_29451823
 */
public class ValidateCodeFilter extends OncePerRequestFilter implements InitializingBean{
	
	private AuthenticationFailureHandler authenticationFailureHandler;

	private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
	
	private Set<String> urls = new HashSet<>();
	
	private SecurityProperties securityProperties;
	
	//用来匹配URL /* 匹配0或者任意数量的字符  ?匹配任何单个字符  /** 匹配0或者任意目录
	private AntPathMatcher pathMatcher = new AntPathMatcher();
	
	@Override
	public void afterPropertiesSet() throws ServletException {
		// 项目启动时要做的事情
		super.afterPropertiesSet();
		String[] config = StringUtils.splitByWholeSeparatorPreserveAllTokens(securityProperties.getValidate().getImage().getUrl(),",");
		for (String configUrl : config) {
			urls.add(configUrl);
		}
		urls.add("/authentication/from");
	}
	
	@Override 
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {
		System.out.println(request.getRequestURI());
		System.out.println(request.getMethod());
		boolean action = false;
		for (String url : urls) {
			if(pathMatcher.match(url, request.getRequestURI())) {
				action = true;
			}
		}
		
/*		if(StringUtils.equals("/authentication/from", request.getRequestURI())
				&& StringUtils.equalsIgnoreCase(request.getMethod(), "POST")) */
		if(action){
			try {
				validate(new ServletWebRequest(request));
			} catch(ValidateCodeException e) {
				//返回异常信息
				authenticationFailureHandler.onAuthenticationFailure(request, response, e);
				return ;
			}
		}
		filterChain.doFilter(request, response);
	}

	/**
	 * @param request
	 * @throws ServletRequestBindingException 
	 */
	private  void validate(ServletWebRequest request) throws ServletRequestBindingException {
		ImageCode codeInSession = (ImageCode)sessionStrategy.getAttribute(request,ValidateCodeController.SESSION_KEY);
		//请求中拿到imageCode参数
	    String codeInRequest = ServletRequestUtils.getStringParameter(request.getRequest(), "imageCode");
	    
	    if(StringUtils.isBlank(codeInRequest)) {
	    	throw new ValidateCodeException("验证码的值不能为空");
	    }
	    
	    if(codeInSession == null) {
	    	throw new ValidateCodeException("验证码不存在");
	    } 
	    
	    if(codeInSession.isExpried()){
	    	sessionStrategy.removeAttribute(request, ValidateCodeController.SESSION_KEY);
	    	throw new ValidateCodeException("验证码已过期");
	    }
	    
	    if(!StringUtils.equals(codeInSession.getCode(), codeInRequest)) {
	    	throw new ValidateCodeException("验证码不匹配");
	    }
	    
	    sessionStrategy.removeAttribute(request, ValidateCodeController.SESSION_KEY);
	}

	public AuthenticationFailureHandler getAuthenticationFailureHandler() {
		return authenticationFailureHandler;
	}

	public void setAuthenticationFailureHandler(AuthenticationFailureHandler authenticationFailureHandler) {
		this.authenticationFailureHandler = authenticationFailureHandler;
	}

	public SessionStrategy getSessionStrategy() {
		return sessionStrategy;
	}

	public void setSessionStrategy(SessionStrategy sessionStrategy) {
		this.sessionStrategy = sessionStrategy;
	}

	public Set<String> getUrls() {
		return urls;
	}

	public void setUrls(Set<String> urls) {
		this.urls = urls;
	}

	public SecurityProperties getSecurityProperties() {
		return securityProperties;
	}

	public void setSecurityProperties(SecurityProperties securityProperties) {
		this.securityProperties = securityProperties;
	}
	
	
}

  1. ImageCode.java 图片验证码
package com.imooc.security.core.validate.code;

import java.awt.image.BufferedImage;
import java.time.LocalDateTime;
/**
 * 图片验证码
 * @author cjj
 * @date 2018年10月8日
 * @email 729165621@qq.com
 * @blog blog.youkuaiyun.com/qq_29451823
 */
public class ImageCode {
	
	//图片
	private BufferedImage image;
	//随机数
	private  String code;
	//过期时间
	private LocalDateTime expireTime;
	
	public ImageCode() {
		super();
	}
	
	public ImageCode(BufferedImage image, String code, int expireTime) {
		super();
		this.image = image;
		this.code = code;
		this.expireTime = LocalDateTime.now().plusSeconds(expireTime);//生成一个过期的时间点
	}
	public BufferedImage getImage() {
		return image;
	}
	public void setImage(BufferedImage image) {
		this.image = image;
	}
	public String getCode() {
		return code;
	}
	public void setCode(String code) {
		this.code = code;
	}
	public LocalDateTime getExpireTime() {
		return expireTime;
	}
	public void setExpireTime(LocalDateTime expireTime) {
		this.expireTime = expireTime;
	}

	public boolean isExpried() {
		return LocalDateTime.now().isAfter(expireTime);//判断当前时间是否在过期时间之后
	}
	
	
}

  1. ValidateCodeException.java 自定义异常处理
package com.imooc.security.core.validate.code;

import org.springframework.security.core.AuthenticationException;
/**
 * 
 * @author cjj
 * @date 2018年10月11日
 * @email 729165621@qq.com
 * @blog blog.youkuaiyun.com/qq_29451823
 */
public class ValidateCodeException extends AuthenticationException {
	
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	public ValidateCodeException(String msg) {
		super(msg);
		// TODO Auto-generated constructor stub
	}

}

效果图
在这里插入图片描述
直接访问这个url时效果图
在这里插入图片描述
12. 当然用户可以自定义图形验证码生成代,例如

package com.imooc.code;

import org.springframework.stereotype.Component;
import org.springframework.web.context.request.ServletWebRequest;

import com.imooc.security.core.validate.code.ImageCode;
import com.imooc.security.core.validate.code.ValidateCodeGenerator;

/**
 * 以增量的方式去适应变化 
 * @author cjj
 * @date 2018年10月26日
 * @email 729165621@qq.com
 * @blog blog.youkuaiyun.com/qq_29451823
 */
@Component("imageCodeGenertor")//直接生成Javabean
public class DemoImageCodeGenerator implements ValidateCodeGenerator {

	@Override
	public ImageCode createImage(ServletWebRequest request) {
		System.out.println("更高级的图形验证码生成代码");
		return null;//如果这样图片就不会显示
	}

}

效果图
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值