本片文章将会在Spring Boot+Spring Security实现自定义登录页登录基础上实现图形验证码验证,阅读本文章前,请先看完前面实现Spring Security自定义登录页文章。

Spring Security - 使用过滤器实现图形验证码
实现思路就是自定义一个专门处理验证码逻辑的过滤器,将其添加到spring security过滤链的合适位置。通过请求获取图形验证码,请求成功的同时将验证码信息保存在session中,当匹配到登录请求时,立刻对验证码进行校验,成功则放行,失败则提前结束整个验证请求。

导入验证码依赖包

这里我们使用的是开源的验证码组件kaptcha,代码如下。

<dependency>
    <groupId>com.github.penggle</groupId>
    <artifactId>kaptcha</artifactId>
    <version>2.3.2</version>
</dependency>

配置kaptcha

这里我们新建一个plugin包后在plugin包里面建一个kaptcha包,包内新建一个kaptcha配置类,代码如下:

package com.security.demo.plugin.kaptcha;

import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.google.code.kaptcha.util.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import java.util.Properties;

@Component
public class Captcha {

    @Bean
    public DefaultKaptcha getDefaultKaptcha(){
        DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
        Properties properties = new Properties();
        // 图片边框
        properties.put("kaptcha.border", "no");
        // 字体颜色
        properties.put("kaptcha.textproducer.font.color", "black");
        // 图片宽
        properties.put("kaptcha.image.width", "100");
        // 图片高
        properties.put("kaptcha.image.height", "40");
        // 字体大小
        properties.put("kaptcha.textproducer.font.size", "25");
        // 验证码长度
        properties.put("kaptcha.textproducer.char.space", "5");
        // 字体
        properties.setProperty("kaptcha.textproducer.font.names", "宋体,楷体,微软雅黑");
        Config config = new Config(properties);
        defaultKaptcha.setConfig(config);
        return defaultKaptcha;
    }
}

创建获取验证码图片接口

创建一个CaptchaControlle用于获取图形验证码,代码如下:

package com.security.demo.controller;

import com.google.code.kaptcha.impl.DefaultKaptcha;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

import javax.imageio.ImageIO;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;

@Controller
public class KaptchaController {

    @Autowired
    private DefaultKaptcha captchaProducer;

    @GetMapping("/captcha.jpg")
    public void defaultKaptcha(HttpServletRequest request, HttpServletResponse response) throws Exception {
        //设置内容类型
        response.setContentType("image/jpeg");
        //创建验证码文本
        String capText = captchaProducer.createText();
        //将验证码文本设置到session
        request.getSession().setAttribute("captcha", capText);
        //创建验证码图片
        BufferedImage capImage = captchaProducer.createImage(capText);
        //获取响应输出流
        ServletOutputStream outputStream = response.getOutputStream();
        //将图片验证码数据写到响应输出流
        ImageIO.write(capImage, "jpg", outputStream);
        //推送并关闭响应输出流
        try {
            outputStream.flush();
        } finally {
            outputStream.close();
        }
    }
}

新增自定义检验异常类

新建包Exception,包内建一个VerificationCodeException 自定义异常类,不要导错包,代码如下:

package com.security.demo.exception;
import org.springframework.security.core.AuthenticationException;

public class VerificationCodeException extends AuthenticationException {

    public VerificationCodeException (String msg){
        super(msg);
    }
}

配置自定义异常处理handler

新建处理器handler包,包内新建MyAuthenticationFailureHandler异常处理类,继承自AuthenticationFailureHandler,代码如下:

package com.security.demo.handler;

import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {

    @Override
    public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException {
        httpServletResponse.setContentType("application/json;charset=UTF-8");
        httpServletResponse.setStatus(401);
        PrintWriter out = httpServletResponse.getWriter();
        out.write("{\n" +
                "    \"error_code\": 401,\n" +
                "    \"error_name\":" + "\"" + e.getClass().getName() + "\",\n" +
                "    \"message\": \"请求失败," + e.getMessage() + "\"\n" +
                "}");
    }
}

配置验证码校验过滤器

在spring 中,推荐通过继承OncePerRequestFilter,它可以保证一次请求只通过一次该过滤器。
创建过滤器包filter,包内新建VerificationCodeFilter类,继承OncePerRequestFilter,代码如下:

package com.security.demo.filter;

import com.security.demo.exception.VerificationCodeException;
import com.security.demo.handler.MyAuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.web.filter.OncePerRequestFilter;
import org.thymeleaf.util.StringUtils;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;

public class VerificationCodeFilter extends OncePerRequestFilter {

    private final  AuthenticationFailureHandler authenticationFailureHandler=new MyAuthenticationFailureHandler();

    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {

        //非登录请求不校验验证码
        if(!"/user/login".equals(httpServletRequest.getRequestURI())){
            filterChain.doFilter(httpServletRequest,httpServletResponse);
        } else {
            try {
                verificationCode(httpServletRequest);
                filterChain.doFilter(httpServletRequest, httpServletResponse);
            } catch (VerificationCodeException e) {
                System.out.println(e);
                authenticationFailureHandler.onAuthenticationFailure(httpServletRequest, httpServletResponse, e);
            } catch (ServletException e) {
                e.printStackTrace();
            }
        }

    }

    public void verificationCode(HttpServletRequest httpServletRequest) throws VerificationCodeException  {
        String requestCode=httpServletRequest.getParameter("captcha");
        HttpSession session=httpServletRequest.getSession();
        String saveCode=(String)session.getAttribute("captcha");
        if(!StringUtils.isEmpty(saveCode)){
            // 校验过一次后清除验证码,不管成功或失败
            session.removeAttribute("captcha");
        }

        //校验不通过抛出异常
        if(StringUtils.isEmpty(requestCode) || StringUtils.isEmpty(saveCode) || !requestCode.equals(saveCode)){
            throw new VerificationCodeException("图形验证码校验异常");
        }

    }
}

更改config配置类

修改WebSecurityConfig类,修改后的代码如下:

package com.security.demo.config;

import com.security.demo.filter.VerificationCodeFilter;
import com.security.demo.handler.MyAuthenticationFailureHandler;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception{
        http.authorizeRequests()
                .antMatchers("/css/**","/img/**","/js/**","/user/login","/login.html","/captcha.jpg").permitAll()
                .anyRequest().authenticated()
                .and()
                //默认都会产生一个hiden标签 里面有安全相关的验证 防止请求伪造 这边我们暂时不需要 可禁用掉
                .csrf().disable()
                .formLogin()
                //指定登录页的路径
                .loginPage("/login.html")
                //指定自定义form表单请求的路径
                .loginProcessingUrl("/user/login")
                .failureUrl("/login?error")
                .defaultSuccessUrl("/success")
                //必须允许所有用户访问我们的登录页(例如未验证的用户,否则验证流程就会进入死循环)
                //这个formLogin().permitAll()方法允许所有用户基于表单登录访问/login这个page。
                .permitAll()
                .failureHandler(new MyAuthenticationFailureHandler());
        //将过滤器添加到UsernamePasswordAuthenticationFilter之前
        http.addFilterBefore(new VerificationCodeFilter(), UsernamePasswordAuthenticationFilter.class);

    }
}

更改登录页面

在之前的页面基础上修改,添加验证码输入框和img标签,修改后的代码如下。

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<link rel="stylesheet" href="/css/style.css" />
		<link rel="stylesheet" href="/css/iconfont.css" />
		<title>登录界面</title>
	</head>
	<body>
		<div id="bigBox">
			<h1>LOGIN</h1>
			<form action="/user/login" method="post">
				<div class="inputBox">
					<div class="inputText">
						<span class="iconfont icon-nickname"></span>
						<input type="text" name="username" placeholder="Username" />
					</div>
					<div class="inputText">
						<span class="iconfont icon-visible"></span>
						<input type="password" name="password" placeholder="Password" />
					</div>
					<div class="inputText">
						<input type="text" style="width: 50px;height: 20px;" name="captcha" placeholder="captcha" />
						<img style="margin-left: 20px;cursor: pointer" src="/captcha.jpg" onclick="this.src='/captcha.jpg?d='+new Date()*1">
					</div>
				</div>
				<input class="loginButton" type="submit" value="Login" />
			</form>
		</div>
	</body>
</html>

测试效果

启动项目,验证码正常显示。

SpringBoot验证码时效 springboot验证码插件_java


输入正确的用户名、正确的密码和正确的验证码,登陆成功

SpringBoot验证码时效 springboot验证码插件_java_02


输入正确的用户名、正确的密码和错误的验证码,提示验证码校验异常。

SpringBoot验证码时效 springboot验证码插件_spring_03

输入正确的用户名、错误的密码和正确的验证码,提示用户名或密码错误

SpringBoot验证码时效 springboot验证码插件_java_04


这篇文章对你有帮助吗?作为一名编程爱好者,在评论区留下你的困惑或你的见解,大家一起来交流吧!