登录验证码
有很多种实现形式,如自定义类UsernamePasswordAuthenticationFilter(默认的用户认证过滤器)、在UsernamePasswordAuthenticationFilter前添加验证码拦截器等。
下面通过第二种实现,在UsernamePasswordAuthenticationFilter前添加验证码拦截器,如果验证码不通过,那么不继续后面的认证。
验证码实现接口
通过hutool插件实现图片验证码,并将验证码写入到session中,key为verifycode,后面校验时需要从session的key中获取验证码真实内容。
@RequestMapping("/getver")
@ResponseBody
public void verifycode(HttpServletResponse response, HttpSession session) {
LineCaptcha captcha = CaptchaUtil.createLineCaptcha(100, 30, 4, 0);
session.setAttribute("verifycode", captcha.getCode());
System.out.println(session);
System.out.println(captcha.getCode());
SecurityContextHolder.clearContext();
try {
ServletOutputStream outputStream = response.getOutputStream();
captcha.write(outputStream);
outputStream.flush();
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}自定义验证码过滤器
自定义验证码过滤器VerifyCodeFilter实现GenericFilter,在VerifyCodeFilter中注入一个MyLoginFailureHandler登录失败处理器来处理验证码错误异常。前端发送的验证码输入框的name必须是code。
@Component
public class VerifyCodeFilter extends GenericFilter {
public final static String CODE_FROM_WEB = "code";
public final static String CODE_FROM_SESSION = "verifycode";
@Autowired
private MyLoginFailureHandler myLoginFailureHandler;
private String defaultFilterProcessUrl = "/login";
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
if("POST".equalsIgnoreCase(request.getMethod()) && defaultFilterProcessUrl.equals(request.getRequestURI())) {
String code = request.getParameter(CODE_FROM_WEB);
String verifycode = (String) request.getSession().getAttribute(CODE_FROM_SESSION);
System.out.println(code + " " + verifycode);
try {
validate(code, verifycode);
request.getSession().removeAttribute(CODE_FROM_SESSION);
} catch (VerifyCodeException e) {
myLoginFailureHandler.onAuthenticationFailure(request, response, e);
return;//如果验证码错误那么直接返回,非常重要。
}
}
filterChain.doFilter(request, response);
}
public void validate(String code, String verifycode) throws InsufficientAuthenticationException {
if(StrUtil.isEmpty(code) || StrUtil.isEmpty(verifycode) || !verifycode.equalsIgnoreCase(code.toLowerCase())) {
throw new VerifyCodeException("验证码错误");
}
}
}
//定义验证码错误异常,需要继承AuthenticationException
class VerifyCodeException extends AuthenticationException {
public VerifyCodeException(String msg) {
super(msg);
}
}丰富MyLoginFailureHandler
MyLoginFailureHandler就是之前定义的用户登陆失败Handler,添加对VerifyCodeException的处理。
@Component
public class MyLoginFailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
HashMap<String, Object> map = new HashMap<>();
String a = "";
if(e instanceof BadCredentialsException) {
a = "密码错误";
} else if(e instanceof DisabledException) {
a = "账户被禁用";
} else if(e instanceof AccountExpiredException) {
a = "账户已过期";
} else if(e instanceof LockedException) {
a = "账户被锁定";
} else if(e instanceof CredentialsExpiredException) {
a = "账户凭证过期";
} else if(e instanceof UsernameNotFoundException) {
//在springsecurity中UsernameNotFoundException被屏蔽无法使用,用户找不到会抛BadCredentialsException
a = "账户不存在";
} else if(e instanceof SessionAuthenticationException && e.getMessage().startsWith("Maximum sessions")){
a = "您已在其他设备登录,禁止登录";
} else if(e instanceof VerifyCodeException){
a = "验证码错误";
} else {
a = "未知错误";
}
System.out.println(e.getMessage());
map.put("code", "401");
map.put("message", "登录失败! " + a);
httpServletResponse.setContentType("application/json;charset=utf-8");
httpServletResponse.getWriter().write(new ObjectMapper().writeValueAsString(map));
}
}配置验证码过滤器生效
配置VerifyCodeFilter在UsernamePasswordAuthenticationFilter认证之前添加自定义过滤器,匹配验证码过滤器。此时在进入UsernamePasswordAuthenticationFilter之前执行verifyCodeFilter进行验证码验证,如果验证码不通过那么不执行后续认证。
@Override
protected void configure(HttpSecurity http) throws Exception {
...省略代码
http.addFilterBefore(verifyCodeFilter, UsernamePasswordAuthenticationFilter.class);
...省略代码
}测试分析过程:
- 在postman中访问http://localhost:8080/getver获取验证码;
- 访问http://localhost:8080/login?username=admin&password=123&code=8d1f,如果不加code或输入的code错误,那么抛出401错误,登录失败验证码错误。
- 访问http://localhost:8080/login?username=admin&password=123&code=8d1f,code正确时提示登录成功。
版权声明:本文为xinshengdelei原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。