在第一篇博客中也说到了springsecurity通过一系列的过滤器将请求进行过滤。我们发现并没有过滤验证码的过滤器,因此我们自己构建一个图片验证码的过滤器来验证验证码。
一、设置二维码实体类,写一个验证验证码是否过期的方法
package com.zhb.security.validate.code;
import java.awt.image.BufferedImage;
import java.time.LocalDateTime;
/**
* @ClassName: ImageCode
* @Description: 二维码信息实体类
* @Author: zhbin
* @CreateDate: 2019/7/9 15:23
* @Version: 1.0
*/
public class ImageCode {
private BufferedImage image;
private String code;
private LocalDateTime expireTime;
public boolean isExpried() {
System.out.println(LocalDateTime.now());
return LocalDateTime.now().isAfter(expireTime);
}
public ImageCode(BufferedImage image, String code, int expireIn) {
this.image = image;
this.code = code;
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;
}
}
二、生成验证码并返回页面
package com.zhb.security.validate.code;
import org.springframework.social.connect.web.HttpSessionSessionStrategy;
import org.springframework.social.connect.web.SessionStrategy;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.ServletWebRequest;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Random;
/**
* @ClassName: ValidateCodeController
* @Description: 生成图片验证码,存放在session中并返回页面
* @Author: zhbin
* @CreateDate: 2019/7/9 15:37
* @Version: 1.0
*/
@RestController
public class ValidateCodeController {
public static final String SESSION_KEY = "SESSION_KEY_IMAGE_CODE";
private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
@GetMapping("/code/image")
public void createCode(HttpServletRequest request, HttpServletResponse response){
/* 获取验证码图片*/
try {
int width=200;
int height=60;
BufferedImage verifyImg=new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);
//生成对应宽高的初始图片
String randomText = drawRandomText(width,height,verifyImg);
// 有效期为60s
ImageCode imageCode = new ImageCode(verifyImg,randomText, 60);
// request.getSession().setAttribute("verifyCode", randomText);
sessionStrategy.setAttribute(new ServletWebRequest(request),SESSION_KEY,imageCode);
response.setContentType("image/png");//必须设置响应内容类型为图片,否则前台不识别
OutputStream os = response.getOutputStream(); //获取文件输出流
ImageIO.write(verifyImg,"png",os);//输出图片流
os.flush();
os.close();//关闭流
} catch (IOException e) {
e.printStackTrace();
}
}
public static String drawRandomText(int width,int height,BufferedImage verifyImg) {
Graphics2D graphics = (Graphics2D)verifyImg.getGraphics();
graphics.setColor(Color.WHITE);//设置画笔颜色-验证码背景色
graphics.fillRect(0, 0, width, height);//填充背景
graphics.setFont(new Font("微软雅黑", Font.BOLD, 40));
//数字和字母的组合
String baseNumLetter = "123456789abcdefghijklmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ";
StringBuffer sBuffer = new StringBuffer();
int x = 10; //旋转原点的 x 坐标
String ch = "";
Random random = new Random();
for(int i = 0;i < 4;i++){
graphics.setColor(getRandomColor());
//设置字体旋转角度
int degree = random.nextInt() % 30; //角度小于30度
int dot = random.nextInt(baseNumLetter.length());
ch = baseNumLetter.charAt(dot) + "";
sBuffer.append(ch);
//正向旋转
graphics.rotate(degree * Math.PI / 180, x, 45);
graphics.drawString(ch, x, 45);
//反向旋转
graphics.rotate(-degree * Math.PI / 180, x, 45);
x += 48;
}
//画干扰线
for (int i = 0; i <6; i++) {
// 设置随机颜色
graphics.setColor(getRandomColor());
// 随机画线
graphics.drawLine(random.nextInt(width), random.nextInt(height),
random.nextInt(width), random.nextInt(height));
}
//添加噪点
for(int i=0;i<30;i++){
int x1 = random.nextInt(width);
int y1 = random.nextInt(height);
graphics.setColor(getRandomColor());
graphics.fillRect(x1, y1, 2,2);
}
return sBuffer.toString();
}
/**
* 随机取色
*/
private static Color getRandomColor() {
Random ran = new Random();
Color color = new Color(ran.nextInt(256),
ran.nextInt(256), ran.nextInt(256));
return color;
}
}
三、继承AuthenticationException 处理验证码异常
package com.zhb.security.validate.code;
import org.springframework.security.core.AuthenticationException;
/**
* @ClassName: ValidateCodeException
* @Description: 验证码异常
* @Author: zhbin
* @CreateDate: 2019/7/9 15:35
* @Version: 1.0
*/
public class ValidateCodeException extends AuthenticationException {
private static final long serialVersionUID = -5012092065788897134L;
public ValidateCodeException(String msg) {
super(msg);
}
}
四、对特定请求进行验证,判读验证码是否正确
package com.zhb.security.validate.code;
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.StringUtils;
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 javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @ClassName: ValidateCodeFilter
* @Description: 图片验证码过滤器
* @Author: zhbin
* @CreateDate: 2019/7/9 15:22
* @Version: 1.0
*/
public class ValidateCodeFilter extends OncePerRequestFilter {
private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
private AuthenticationFailureHandler authenticationFailureHandler;
@Override
/**
* 将请求地址为/authentication/form并且为“post”提交的所有请求进行验证
*/
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
if(request.getRequestURI().equals("/authentication/form") && "post".equalsIgnoreCase(request.getMethod())){
try {
validate(new ServletWebRequest(request));
}catch (ValidateCodeException e){
authenticationFailureHandler.onAuthenticationFailure(request,response,e);
return;
}
}
filterChain.doFilter(request, response);
}
/**
* 验证验证码的合法性
* @param request
*/
private void validate(ServletWebRequest request){
ImageCode codeInSession = (ImageCode) sessionStrategy.getAttribute(request, ValidateCodeController.SESSION_KEY);
String codeInRequest = "";
try {
// 获取提交的验证码
codeInRequest = ServletRequestUtils.getStringParameter(request.getRequest(), "imageCode");
} catch (ServletRequestBindingException e) {
throw new ValidateCodeException("获取验证码的值失败");
}
if (StringUtils.isEmpty(codeInRequest)) {
throw new ValidateCodeException("验证码的值不能为空");
}
if (codeInSession == null) {
throw new ValidateCodeException("验证码不存在");
}
if (codeInSession.isExpried()) {
sessionStrategy.removeAttribute(request, ValidateCodeController.SESSION_KEY);
throw new ValidateCodeException("验证码已过期");
}
if (!codeInSession.getCode().equalsIgnoreCase(codeInRequest)) {
throw new ValidateCodeException("验证码不匹配");
}
sessionStrategy.removeAttribute(request, ValidateCodeController.SESSION_KEY);
}
public AuthenticationFailureHandler getAuthenticationFailureHandler() {
return authenticationFailureHandler;
}
public void setAuthenticationFailureHandler(AuthenticationFailureHandler authenticationFailureHandler) {
this.authenticationFailureHandler = authenticationFailureHandler;
}
}
五、配置自定义的验证码过滤器,同时配置不拦截获取验证码的路径
protected void configure(HttpSecurity http) throws Exception {
ValidateCodeFilter validateCodeFilter = new ValidateCodeFilter();
validateCodeFilter.setAuthenticationFailureHandler(failHandler); // 使用我们自定义的失败处理器
http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class) // 在UsernamePasswordAuthenticationFilter过滤器前面增加验证码过滤器
.formLogin()
.loginPage("/authentication/require")
.loginProcessingUrl("/authentication/form")
.successHandler(authentication)
.failureHandler(failHandler)
.and()
.authorizeRequests()
.antMatchers("/authentication/require",
securityProperties.getProperties().getLoginPage(),
"/code/image" ).permitAll() // 不拦截/code/image
.anyRequest()
.authenticated()
.and()
.csrf().disable();
}
这样就在springsecurity中完成了登录时验证码验证的功能。