一、引入验证码模块: {#toc_0}
通过以下maven依赖将验证码模块引入项目中
<!-- 验证码 -->
<dependency>
<groupId>com.github.whvcse</groupId>
<artifactId>easy-captcha</artifactId>
<version>1.6.2</version>
</dependency>
二、验证码接口: {#toc_1}
创建一个Controller,在里面写上如下代码: 生成验证码和一个随机数,并将随机数和验证码作为键和值存入redis缓存中,同时返回随机数和验证码图片的base64编码。
@GetMapping("/code")
public ResponseEntity captcha() {
SpecCaptcha specCaptcha = new SpecCaptcha(130, 50, 5);
String verCode = specCaptcha.text().toLowerCase();
String key = UUID.randomUUID().toString();
log.info("code: {}, key: {}", verCode, key);
// 存入redis并设置过期时间为5分钟
redisService.setCacheObject("code::" + key, verCode, 5, TimeUnit.MINUTES);
// 将key和base64返回给前端
Map<String, Object> map = new HashMap<>();
map.put("key", key);
map.put("image", specCaptcha.toBase64());
return Result.success(map);
}
三、编写过滤器拦: {#toc_2}
创建一个类继承GenericFilterBean类,重写doFilter方法。对请求的参数中验证码部分进行拦截校验。
@Component
public class VerifyCodeFilter extends GenericFilterBean {
@Autowired
private RedisService redisService;
private String defaultFilterProcessUrl = "/login";
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
if ("POST".equalsIgnoreCase(request.getMethod()) && defaultFilterProcessUrl.equals(request.getServletPath())) {
// 验证码验证
String requestCaptcha = request.getParameter("code");
String key = request.getParameter("key");
if (StringUtils.isEmpty(requestCaptcha)) {
ResponseUtil.outMessage(response, ResponseUtil.resultMap(false, 400, "登录失败,验证码不能为空!"));
throw new CustomException("登录失败!验证码不能为空!");
}
if (StringUtils.isEmpty(key)) {
ResponseUtil.outMessage(response, ResponseUtil.resultMap(false, 400, "登录失败,参数不足!"));
throw new CustomException("参数不足!登录失败");
}
String originCode = redisService.getCacheObject("code::" + key.trim().toLowerCase(Locale.ROOT));
redisService.deleteObject("code::" + key);
if (StringUtils.isNull(originCode)) {
ResponseUtil.outMessage(response, ResponseUtil.resultMap(false, 400, "登录失败,验证码已过期!"));
throw new CustomException("登录失败,验证码已过期!");
}
if (!originCode.equalsIgnoreCase(requestCaptcha)) {
ResponseUtil.outMessage(response, ResponseUtil.resultMap(false, 400, "登录失败,验证码错误"));
throw new CustomException("验证码错误!");
}
chain.doFilter(request, response);
}
}
`}`
ResponseUtil工具类的outMessage方法如下 :
public static void outMessage(HttpServletResponse response, Map<String, Object> resultMap) {
ServletOutputStream outputStream = null;
try {
outputStream = response.getOutputStream();
outputStream.write(JSON.toJSONString(resultMap).getBytes(StandardCharsets.UTF_8));
outputStream.flush();
} catch (IOException e) {
} finally {
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
}
}
}
}
四、将过滤器加入到Spring Security的过滤器链中,且在账号密码校验之前。 {#toc_3}
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
VerifyCodeFilter verifyCodeFilter;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(verifyCodeFilter,UsernamePasswordAuthenticationFilter.class)
//其余配置
}
}
总结: {#toc_4}
获取验证码 >> 将验证码存入缓存同时响应到前端 >> 登录时带上验证码和key >> 拦截器拦截登录请求 >> 根据key从缓存获取验证码进行校验
最重要的就是将写好的过滤器放到正确的位置,如这里将自定义验证码校验过滤器verifyCodeFilter放到账号密码认证过滤器UsernamePasswordAuthenticationFilter之前:
http.addFilterBefore(verifyCodeFilter,UsernamePasswordAuthenticationFilter.class)