1. 生成验证码的工具类
这个工具类很常见,网上也有很多,就是画一个简单的验证码,通过流将验证码写到前端页面。
import javax.imageio.ImageIO; import java.awt.*; import java.awt.image.BufferedImage; import java.io.IOException; import java.io.OutputStream; import java.util.Random; /** * @Author: gaoyang * @Date: 2020/7/8 14:20 * @Description: 生成验证码的工具类 */ public class VerificationCode { private int width = 100;// 生成验证码图片的宽度 private int height = 30;// 生成验证码图片的高度 private String[] fontNames = { "宋体", "楷体", "隶书", "微软雅黑" }; private Color bgColor = new Color(255, 255, 255);// 定义验证码图片的背景颜色为白色 private Random random = new Random(); private String codes = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; private String text;// 记录随机字符串 /** * 获取一个随意颜色 * * @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 codes.charAt(random.nextInt(codes.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 - 8); } this.text = 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); } } public String getText() { return text; } public static void output(BufferedImage image, OutputStream out) throws IOException { ImageIO.write(image, "JPEG", out); } }
2. 验证码测试接口
import com.javaboy.vhr.utils.RespBean; import com.javaboy.vhr.utils.VerificationCode; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.awt.image.BufferedImage; import java.io.IOException; /** * @Author: gaoyang * @Date: 2020/10/24 15:05 * @Description: */ @Api(tags = "未登录API") @RestController public class LoginController { @ApiOperation(value = "生成验证码图片") @GetMapping("/verifyCode") public void verifyCode(HttpServletRequest request, HttpServletResponse resp) throws IOException { VerificationCode code = new VerificationCode(); BufferedImage image = code.getImage(); String text = code.getText(); HttpSession session = request.getSession(true); session.setAttribute("verify_code", text); VerificationCode.output(image,resp.getOutputStream()); } }
到这验证码生成已经实现,下面是集成到SpringSecurity需要做的事情。
3. 验证码过滤器
import com.fasterxml.jackson.databind.ObjectMapper; import com.javaboy.vhr.utils.RespBean; import org.springframework.stereotype.Component; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; /** * @author: gaoyang * @date: 2020-12-27 14:20 * @description: 验证码过滤器 */ @Component public class VerificationCodeFilter extends GenericFilter { @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) servletRequest; HttpServletResponse resp = (HttpServletResponse) servletResponse; if ("POST".equals(req.getMethod()) && "/doLogin".equals(req.getServletPath())) { //登录请求 String code = req.getParameter("code"); String verify_code = (String) req.getSession().getAttribute("verify_code"); if (code == null || "".equals(code) || !verify_code.toLowerCase().equals(code.toLowerCase())) { //验证码不正确 resp.setContentType("application/json;charset=utf-8"); PrintWriter out = resp.getWriter(); out.write(new ObjectMapper().writeValueAsString(RespBean.error("验证码错误!"))); out.flush(); out.close(); return; } else { filterChain.doFilter(req, resp); } } else { filterChain.doFilter(req, resp); } } }
自定义过滤器继承自 GenericFilterBean,并实现其中的 doFilter 方法,在 doFilter 方法中,当请求方法是 POST,并且请求地址是 /doLogin 时,获取参数中的 code 字段值,该字段保存了用户从前端页面传来的验证码,然后获取 session 中保存的验证码,如果用户没有传来验证码,则抛出验证码不能为空异常,如果用户传入了验证码,则判断验证码是否正确,如果不正确则抛出异常,否则执行 chain.doFilter(request, response); 使请求继续向下走。
4. Spring Security配置类引入验证码过滤器
@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Resource private VerificationCodeFilter verificationCodeFilter; @Override public void configure(WebSecurity web) throws Exception { web.ignoring().antMatchers( "/verifyCode"); } @Override protected void configure(HttpSecurity http) throws Exception { // 验证码拦截器 http.addFilterBefore(verificationCodeFilter, UsernamePasswordAuthenticationFilter.class); http.authorizeRequests() .antMatchers("/admin/**").hasRole("admin") ... ... .permitAll() .and() .csrf().disable(); } }
这里只贴出了部分核心代码,即 http.addFilterBefore(verifyCodeFilter, UsernamePasswordAuthenticationFilter.class); ,如此之后,整个配置就算完成了。
接下来在登录中,就需要传入验证码了,如果不传或者传错,都会抛出异常,例如不传的话,抛出如下异常:
5. 效果图