持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第13天,点击查看活动详情
前后端分离登录添加验证码
大家对验证码都不陌生,那验证码的作用大家知道吗?验证码一般是防止有人利用机器人自动批量注册、对特定的注册用户用特定程序暴力破解方式进行不断的登陆、灌水以及恶意占用网站的资源。因为验证码是一个混合了数字或符号的图片,机器识别起来比较困难。实际上用验证码是现在很多网站通用的方式,一般登录注册都会有验证码的身影。
接下来就看看使用了Spring Security的前后端分离项目,怎么添加使用使用验证码功能。在上一篇,我们完成了基本的登录方法,只能使用数据库的用户信息进行登录,但是登录时没有用到验证码,接下来需要我们进行完善。
1. 思路分析
验证码肯定不是由Security产生的,需要我们自己进行产生并添加到session中,这样进行验证码匹配的时候就可以直接从session中获取,与用户输入的验证码进行比较,然后通过提示告知用户是否输入错误。
2. 生成验证码
这里我们使用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);}
}
这里分析一下验证码的产生,编写的配置类就是为了配置验证码的属性,以及产生一个提供器,提供器Producer的类是Google提供的,可以使用Producer产生一串字符串,控制器上session的目的是为了携带验证码。因为前后端是通过Json来传递数据的,所以需要将验证码产生的图片以base64的格式进行传输,前端拿到后在意相同的格式进行还原,就可以展示包含验证码的图片了。我们验证的时候就可以通过解析工具将图片解析出来(注意第二场图片方框里的前缀),再进行登录请求。
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);
}
我们需要先获取到用户的输入,拿到验证码,先对验证码进行比较,再对用户名和密码进行比较,如果if里面的代码没通过,就会直接抛异常,而不会用户名密码进行比较。这里的getKaptchaParameter()方法是在自定义的过滤器里面添加了一个 KAPTCHA_KEY 用来指定默认的验证码输入的参数,同时提供get/set方法用以验证码的设置获取。
注入自定义过滤器的时候,通过loginFilter.setKaptchaParameter("kaptcha");设置参数。
这就是验证码的添加过程,其实过程不复杂,只是在原有登录的基础上先进行验证码的匹配并进行提示,这仅仅是后端的实现,没有页面,大佬们可以自定义页面进行更加完整验证。