系列十、Spring Security登录接口添加验证码

一、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);
    }
}

1.5、测试

1.5.1、生成验证码

1.5.2、登录

相关推荐
那你为何对我三笑留情19 小时前
六、Spring Boot集成Spring Security之前后分离项目认证流程最佳方案
java·spring boot·分布式·后端·spring·spring security
ccmjga10 天前
建造者设计模式
java·spring boot·设计模式·gradle·spring security·1024程序员节
ccmjga11 天前
为什么选择 Spring data hadoop
java·spring boot·docker·设计模式·gradle·spring security
ccmjga14 天前
适配器设计模式
java·spring boot·后端·设计模式·gradle·spring security·1024程序员节
那你为何对我三笑留情25 天前
五、Spring Boot集成Spring Security之认证流程2
spring boot·spring security
那你为何对我三笑留情1 个月前
三、Spring Boot集成Spring Security之securityFilterChain过滤器链详解
java·spring boot·spring·spring security·过滤器链
DeskPins1 个月前
OAuth2.0进阶
oauth2.0
那你为何对我三笑留情1 个月前
二、Spring Boot集成Spring Security之实现原理
java·spring boot·spring·spring security