系列十、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、登录

相关推荐
G皮T2 个月前
【Spring Boot】用 Spring Security 实现后台登录及权限认证功能
spring boot·安全·spring·spring security·认证·登录·授权
左直拳2 个月前
Spring Boot项目的控制器貌似只能get不能post问题
spring boot·spring security·csrf·post不行
代码匠心3 个月前
从零开始学Spring Boot系列-集成Spring Security实现用户认证与授权
java·后端·springboot·spring security
langzitianya3 个月前
Spring Security6 设置免登录接口地址
java·后端·spring·spring security
兴趣广泛的程序猿3 个月前
关于Spring Security的CORS
spring·spring security·cors
Jack_hrx3 个月前
详解 Spring Security:全面保护 Java 应用程序的安全框架
java·安全·spring·spring cloud·spring security
Maiko Star3 个月前
(新)Spring Security如何实现登录认证(实战篇)
服务器·spring security
不当菜虚困3 个月前
Spring Security实现用户认证四:使用JWT与Redis实现无状态认证
java·redis·spring·spring security
提伯斯4 个月前
oidc-client.js踩坑吐槽贴
oauth2.0
淼淼分享师4 个月前
深入理解Spring Security:认证机制解析
java·后端·spring·spring security