Spring Security前后端分离登录添加验证码

121 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第13天,点击查看活动详情

前后端分离登录添加验证码

\quad大家对验证码都不陌生,那验证码的作用大家知道吗?验证码一般是防止有人利用机器人自动批量注册、对特定的注册用户用特定程序暴力破解方式进行不断的登陆、灌水以及恶意占用网站的资源。因为验证码是一个混合了数字或符号的图片,机器识别起来比较困难。实际上用验证码是现在很多网站通用的方式,一般登录注册都会有验证码的身影。
\quad接下来就看看使用了Spring Security的前后端分离项目,怎么添加使用使用验证码功能。在上一篇,我们完成了基本的登录方法,只能使用数据库的用户信息进行登录,但是登录时没有用到验证码,接下来需要我们进行完善。

1. 思路分析

\quad验证码肯定不是由Security产生的,需要我们自己进行产生并添加到session中,这样进行验证码匹配的时候就可以直接从session中获取,与用户输入的验证码进行比较,然后通过提示告知用户是否输入错误。

2. 生成验证码

\quad这里我们使用Google提供的依赖进行生成,然后需要编写一个controller,返回生成的验证码,最后需要自定义验证码不匹配的异常。

<!-- 导入依赖 -->
<dependency>
    <groupId>com.github.penggle</groupId>
    <artifactId>kaptcha</artifactId>
    <version>2.3.2</version>
</dependency>

//验证码的配置
@Configuration
public class KaptchaConfig {
    @Bean
    public Producer kaptcha(){
        Properties properties = new Properties();
        properties.setProperty("kaptcha.image.width","150");
        properties.setProperty("kaptcha.image.height","50");
        properties.setProperty("kaptcha.testproducer.char.string","0123456789");
        properties.setProperty("kaptcha.testproducer.length","4");
        Config config = new Config(properties);
        DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
        defaultKaptcha.setConfig(config);
        return defaultKaptcha;

    }
}

//验证码控制器
public class VerifyCodeController {

    @Autowired
    private Producer producer;

    @GetMapping("/vc.jpg")
    public String getVerifyCode(HttpSession session) throws IOException {
        //1.生成验证码
        String text = producer.createText();
        //2.放入 session,也可以是 redis 实现
        session.setAttribute("kaptcha", text);
        //3.生成图片
        BufferedImage bi = producer.createImage(text);
        FastByteArrayOutputStream fos = new FastByteArrayOutputStream();
        ImageIO.write(bi, "jpg", fos);
        //4.返回base64
        return Base64.encodeBase64String(fos.toByteArray());
    }
}

//自定义异常
public class KaptchaNotMatchException extends AuthenticationException {
    public KaptchaNotMatchException(String msg) {super(msg);}

    public KaptchaNotMatchException(String msg,Throwable cause){super(msg,cause);}
}

\quad这里分析一下验证码的产生,编写的配置类就是为了配置验证码的属性,以及产生一个提供器,提供器Producer的类是Google提供的,可以使用Producer产生一串字符串,控制器上session的目的是为了携带验证码。因为前后端是通过Json来传递数据的,所以需要将验证码产生的图片以base64的格式进行传输,前端拿到后在意相同的格式进行还原,就可以展示包含验证码的图片了。我们验证的时候就可以通过解析工具将图片解析出来(注意第二场图片方框里的前缀),再进行登录请求。

image.png

image.png

image.png

3.验证码匹配

@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
        throws AuthenticationException {
        if (!request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported:" + request.getMethod());
        }
        if (request.getContentType().equalsIgnoreCase(MediaType.APPLICATION_JSON_VALUE)) {
            Map<String, String> userInfo = new HashMap<>();
            try {
                userInfo = new ObjectMapper().readValue(request.getInputStream(), Map.class);
                String username = userInfo.get(getUsernameParameter());
                String password = userInfo.get(getPasswordParameter());
                String katcha = userInfo.get(getKaptchaParameter());

                //获取验证码
                String sessionVerifyCode = (String) request.getSession().getAttribute("kaptcha");
                if (!ObjectUtils.isEmpty(katcha) && !ObjectUtils.isEmpty(sessionVerifyCode) && katcha.equalsIgnoreCase(sessionVerifyCode)){
                    UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
                    setDetails(request, authRequest);
                    return this.getAuthenticationManager().authenticate(authRequest);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            throw new KaptchaNotMatchException("验证码输入错误!");
        }
        return super.attemptAuthentication(request, response);
}

\quad我们需要先获取到用户的输入,拿到验证码,先对验证码进行比较,再对用户名和密码进行比较,如果if里面的代码没通过,就会直接抛异常,而不会用户名密码进行比较。这里的getKaptchaParameter()方法是在自定义的过滤器里面添加了一个 KAPTCHA_KEY 用来指定默认的验证码输入的参数,同时提供get/set方法用以验证码的设置获取。
\quad注入自定义过滤器的时候,通过loginFilter.setKaptchaParameter("kaptcha");设置参数。 image.png

\quad这就是验证码的添加过程,其实过程不复杂,只是在原有登录的基础上先进行验证码的匹配并进行提示,这仅仅是后端的实现,没有页面,大佬们可以自定义页面进行更加完整验证。