一、Spring Security登录接口添加验证码
1.1、概述
一般企业开发中,登录时都会有一个验证码,基于Spring Security的登录接口默认是没有验证码的?那么如何把验证码功能集成到Spring Security的登录接口呢?请看下文!
1.2、生成验证码
1.2.1、 VerifyCode7006
java
/**
* @Author : 一叶浮萍归大海
* @Date: 2024/1/12 17:31
* @Description: 验证码实体类
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
@ToString(callSuper = true)
public class VerifyCode7006 {
/**
* 生成验证码图片的宽度
*/
private Integer width = 100;
/**
* 生成验证码图片的高度
*/
private Integer height = 50;
/**
* 字体集合
*/
private String[] fontNames = {"宋体", "楷体", "隶书", "微软雅黑"};
/**
* 定义验证码图片的背景颜色为白色
*/
private Color bgColor = new Color(255, 255, 255);
/**
* 定义随机数
*/
private Random random = new Random();
/**
* 混淆代码
* confuse:混淆
*/
private String confuseCode = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
/**
* 记录随机生成的验证码
*/
private String code;
/**
* 获取一个随意颜色
*
* @return
*/
private Color randomColor() {
int red = random.nextInt(150);
int green = random.nextInt(150);
int blue = random.nextInt(150);
return new Color(red, green, blue);
}
/**
* 获取一个随机字体
*
* @return
*/
private Font randomFont() {
String name = fontNames[random.nextInt(fontNames.length)];
int style = random.nextInt(4);
int size = random.nextInt(5) + 24;
return new Font(name, style, size);
}
/**
* 获取一个随机字符
*
* @return
*/
private char randomChar() {
return confuseCode.charAt(random.nextInt(confuseCode.length()));
}
/**
* 创建一个空白的BufferedImage对象
*
* @return
*/
private BufferedImage createImage() {
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = (Graphics2D) image.getGraphics();
/**
* 设置验证码图片的背景颜色
*/
g2.setColor(bgColor);
g2.fillRect(0, 0, width, height);
return image;
}
public BufferedImage getImage() {
BufferedImage image = createImage();
Graphics2D g2 = (Graphics2D) image.getGraphics();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < 4; i++) {
String s = randomChar() + "";
sb.append(s);
g2.setColor(randomColor());
g2.setFont(randomFont());
float x = i * width * 1.0f / 4;
g2.drawString(s, x, height - 15);
}
this.code = sb.toString();
// drawLine(image);
return image;
}
/**
* 绘制干扰线
*
* @param image
*/
private void drawLine(BufferedImage image) {
Graphics2D g2 = (Graphics2D) image.getGraphics();
int num = 5;
for (int i = 0; i < num; i++) {
int x1 = random.nextInt(width);
int y1 = random.nextInt(height);
int x2 = random.nextInt(width);
int y2 = random.nextInt(height);
g2.setColor(randomColor());
g2.setStroke(new BasicStroke(1.5f));
g2.drawLine(x1, y1, x2, y2);
}
}
}
1.2.2、VerifyCodeUtil7006
java
/**
* @Author : 一叶浮萍归大海
* @Date: 2024/1/12 18:18
* @Description: 验证码工具类
*/
public class VerifyCodeUtil7006 {
/**
* 获取验证码的image 和 text
* @return
*/
public static Map<String,Object> initVerifyCode() {
Map<String,Object> verifyCodeMap = new HashMap<>(2);
VerifyCode7006 vc = new VerifyCode7006();
BufferedImage image = vc.getImage();
String code = vc.getCode();
verifyCodeMap.put("image",image);
verifyCodeMap.put("code",code);
return verifyCodeMap;
}
/**
* 输出验证码
* @param image
* @param out
* @throws IOException
*/
public static void output(BufferedImage image, OutputStream out) throws IOException {
ImageIO.write(image, "JPEG", out);
}
}
1.2.3、VerifyCodeController7006
java
/**
* @Author : 一叶浮萍归大海
* @Date: 2024/1/12 22:28
* @Description:
*/
@RequestMapping("/verifyCode")
@RestController
public class VerifyCodeController7006 {
/**
* 获取验证码
*
* @param request
* @param response
*/
@GetMapping("/getVerifyCode")
public void getVerifyCode(HttpServletRequest request, HttpServletResponse response) throws IOException {
Map<String, Object> verifyCodeMap = VerifyCodeUtil7006.initVerifyCode();
BufferedImage image = (BufferedImage) verifyCodeMap.get("image");
String code = (String) verifyCodeMap.get("code");
HttpSession session = request.getSession();
session.setAttribute("code", code);
VerifyCodeUtil7006.output(image,response.getOutputStream());
}
}
1.3、配置类(核心部分)
java
@Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(verifyCodeFilter, UsernamePasswordAuthenticationFilter.class);
http.authorizeRequests()
.antMatchers("/dba/**").hasRole("dba")
.antMatchers("/admin/**").hasRole("admin")
.antMatchers("/helloWorld","/verifyCode/getVerifyCode")
.permitAll()
.anyRequest()
.authenticated()
.and()
/**
* 登录成功 & 登录失败回调
*/
.formLogin()
.loginPage("/login")
.successHandler(successHandler)
.failureHandler(failureHandler)
.and()
/**
* 注销登录回调
*/
.logout()
.logoutUrl("/logout")
.logoutSuccessHandler(logoutSuccessHandler)
.permitAll()
.and()
.csrf()
.disable()
/**
* 未认证 & 权限不足回调
*/
.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
.accessDeniedHandler(accessDeniedHandler);
}
1.4、过滤器
java
/**
* @Author : 一叶浮萍归大海
* @Date: 2024/1/12 19:49
* @Description: 自定义验证码过滤器
* 作用:
* 自定义过滤器继承自 GenericFilterBean,并实现其中的 doFilter 方法,在 doFilter 方法中,当请求方法是 POST,
* 并且请求地址是 /login 时,获取参数中的 code 字段值,该字段保存了用户从前端页面传来的验证码,然后获取 session 中保存的验证码,
* 如果用户没有传来验证码,则抛出验证码不能为空异常,如果用户传入了验证码,则判断验证码是否正确,如果不正确则抛出异常,
* 否则执行 chain.doFilter(request, response); 使请求继续向下走。
*/
@Slf4j
@Component
public class MyVerifyCodeFilter extends GenericFilterBean {
private static final String DEFAULT_FILTER_PROCESS_URL = "/login";
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
HttpSession session = request.getSession();
log.info("session:{}", session);
if (HttpMethod.POST.name().equalsIgnoreCase(request.getMethod()) && DEFAULT_FILTER_PROCESS_URL.equals(request.getServletPath())) {
String paramCode = request.getParameter("code");
String sessionCode = (String) session.getAttribute("code");
log.info("paramCode:{},sessionCode:{}", paramCode, sessionCode);
if (StringUtils.isBlank(paramCode)) {
R r = R.error(ResponseEnum.VERIFY_CODE_IS_NULL.getCode(), ResponseEnum.VERIFY_CODE_IS_NULL.getMessage());
response.setContentType("application/json;charset=utf-8");
PrintWriter out = response.getWriter();
out.write(new ObjectMapper().writeValueAsString(r));
out.flush();
out.close();
}
if (StringUtils.isBlank(sessionCode)) {
R r = R.error(ResponseEnum.VERIFY_CODE_IS_EXPIRED.getCode(), ResponseEnum.VERIFY_CODE_IS_EXPIRED.getMessage());
response.setContentType("application/json;charset=utf-8");
PrintWriter out = response.getWriter();
out.write(new ObjectMapper().writeValueAsString(r));
out.flush();
out.close();
}
if (!StringUtils.equals(paramCode.toLowerCase(), sessionCode.toLowerCase())) {
R r = R.error(ResponseEnum.VERIFY_CODE_IS_NOT_MATCH.getCode(), ResponseEnum.VERIFY_CODE_IS_NOT_MATCH.getMessage());
response.setContentType("application/json;charset=utf-8");
PrintWriter out = response.getWriter();
out.write(new ObjectMapper().writeValueAsString(r));
out.flush();
out.close();
}
}
chain.doFilter(request, response);
}
}