本片文章将会在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>
测试效果
启动项目,验证码正常显示。
输入正确的用户名、正确的密码和正确的验证码,登陆成功
输入正确的用户名、正确的密码和错误的验证码,提示验证码校验异常。
输入正确的用户名、错误的密码和正确的验证码,提示用户名或密码错误
这篇文章对你有帮助吗?作为一名编程爱好者,在评论区留下你的困惑或你的见解,大家一起来交流吧!