SpringSecurity实现图形验证码

SpringSecurity实现图形验证码

1.开发生成图形验证码接口

-> 封装ImageCode对象,来存放图片验证码的内容、图片以及有效时间

public class ImageCode {
	private BufferedImage image;// 图片
	private String code;// 验证码
	private LocalDateTime expireTime;// 有效时间
	public ImageCode(BufferedImage image, String code, int expireIn) {
		this.image = image;
		this.code = code;
		// 出入一个秒数,自动转为时间,如过期时间为60s,这里的expireIn就是60,转换为当前时间上加上这个秒数
		this.expireTime = LocalDateTime.now().plusSeconds(expireIn);
	}
	public ImageCode(BufferedImage image, String code, LocalDateTime expireTime) {
		this.image = image;
		this.code = code;
		this.expireTime = 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;
	}
}

-> 写一个Controller用于生成图片和校验验证码

public class ValidateCodeController {
	private static final String SESSION_KEY = "SESSION_KEY_IMAGE_CODE";
	private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
	@GetMapping("/code/image")
	public void createCode(HttpServletRequest request, HttpServletResponse response) throws IOException {
		// 根据随机数生成图片
		ImageCode imageCode = createImageCode(request);
		// 将随机数存到session中
		sessionStrategy.setAttribute(new ServletWebRequest(request), SESSION_KEY, imageCode);
		// 将生成的图片写到接口的响应中
		ImageIO.write(imageCode.getImage(), "JPEG", response.getOutputStream());
	}

	private ImageCode createImageCode(HttpServletRequest request) {
		// 图片的宽高(像素)
		int width = 67;
		int height = 23;
		// 生成图片对象
		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, xl, yl);
		}
		
		// 生成四位随机数
		String sRand = "";
		for (int i = 0; i < 4; 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();
		// 60秒有效
		return new ImageCode(image, sRand, 60);
	}

	/**
	 * 生成随机背景条纹
	 * @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);
	}
}

第一步:根据随机数生成图片

ImageCode imageCode = createImageCode(request);

第二步:将随机数存到session中

sessionStrategy.setAttribute(new ServletWebRequest(request), SESSION_KEY, imageCode);

第三步:将生成的图片写到接口的响应中

ImageIO.write(imageCode.getImage(), “JPEG”, response.getOutputStream());

-> 在静态页面中加入图片验证码的标签

<tr>
	<td>图形验证码:</td>
	<td>
		<input type="text" name="imageCode">
		<img src="/code/image">
	</td>
</tr>

-> 将接口请求地址配进认证

@Override
protected void configure(HttpSecurity http) throws Exception {
	http.formLogin()
		.loginPage("/authencation/require")
		.loginProcessingUrl("/authentication/form")
		.successHandler(imoocAuthenticationSuccessHandler)
		.failureHandler(imoocAuthenticationFailureHandler)
		.and()
		.authorizeRequests()
		.antMatchers("/authencation/require", 
				securityPropertis.getBrowserPropertis().getLoginPage(),
				"/code/image").permitAll()   // 加入"/code/image"地址
		.anyRequest()
		.authenticated()
		.and()
		.csrf().disable();
}

->启动服务器访问静态表单
如图所示:
SpringSecurity实现图形验证码
图片验证码已经显示出来了

2.在认证流程中加入图形验证码校验

-> 写一个filter进行拦截

public class ValidateCodeFilter extends OncePerRequestFilter{
	private AuthenticationFailureHandler authenticationFailureHandler;
	private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
	@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {
		//如果访问的是/authentication/form并且为post请求
		if(StringUtils.equals("/authentication/form", request.getRequestURI())
				&& StringUtils.equals(request.getMethod(), "post")) {
			try {
				// 验证图片验证码是否填写正确
				validate(new ServletWebRequest(request));
			} catch (ValidateCodeException e) {
				// 抛出异常,并返回,不再访问资源
				authenticationFailureHandler.onAuthenticationFailure(request, response, e);
				return;
			}
		}
		// 通过,执行后面的filter
		filterChain.doFilter(request, response);
	}
	// 校验验证码的逻辑
	private void validate(ServletWebRequest request) throws ServletRequestBindingException {
		ImageCode codeInSession = (ImageCode) sessionStrategy.getAttribute(request, ValidateCodeController.SESSION_KEY);
		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;
	}
}

-> 配置再configure中,生效

@Override
protected void configure(HttpSecurity http) throws Exception {
	// 声明filter
	ValidateCodeFilter validateCodeFilter = new ValidateCodeFilter();
	// 配置验证失败执行的handler
	validateCodeFilter.setAuthenticationFailureHandler(imoocAuthenticationFailureHandler);
	// 添加filter到认证流程
	http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class)
		.formLogin()
		.loginPage("/authencation/require")
		.loginProcessingUrl("/authentication/form")
		.successHandler(imoocAuthenticationSuccessHandler)
		.failureHandler(imoocAuthenticationFailureHandler)
		.and()
		.authorizeRequests()
		.antMatchers("/authencation/require", 
				securityPropertis.getBrowserPropertis().getLoginPage(),
				"/code/image").permitAll()
		.anyRequest()
		.authenticated()
		.and()
		.csrf().disable();
}

至此,图片验证码验证流程已经全部完成。
启动服务,进行测试即可。