赞
踩
本片文章将会在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>
这里我们新建一个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包,包内新建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("图形验证码校验异常"); } } }
修改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>
启动项目,验证码正常显示。
输入正确的用户名、正确的密码和正确的验证码,登陆成功
输入正确的用户名、正确的密码和错误的验证码,提示验证码校验异常。
输入正确的用户名、错误的密码和正确的验证码,提示用户名或密码错误
这篇文章对你有帮助吗?作为一名编程爱好者,在评论区留下你的困惑或你的见解,大家一起来交流吧!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。