在 Spring Security 中自定义一个的过滤器,将其添加到 Spring Security 过滤器链的合适位置。定义一个自己的过滤器类继承 Filter 接口即可。
但是在 Spring 体系中,推荐使用 OncePerRequestFilter 来实现,它可以确保一次请求只会通过一次该过滤器(Filter 实际上并不能保证这 一点)。
public class MySecurityFilter extends OncePerRequestFilter {@Overrideprotected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {// 非登录请求,不处理if("/login".equals(httpServletRequest.getRequestURI())&&httpServletRequest.getMethod().equals(HttpMethod.POST.name())) {String username = httpServletRequest.getParameter("username");String password = httpServletRequest.getParameter("password");System.out.println("username:" + username);System.out.println("password:" + password);}else {System.out.println("非登录处理!");}filterChain.doFilter(httpServletRequest,httpServletResponse);}}
创建 Spring Security 配置类,继承WebSecurityConfigurerAdapter, 重写方法void configure(HttpSecurity http), 将自定义的过滤器添加到 Spring Security 过滤器链中:
@EnableWebSecuritypublic class WebSecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {super.configure(http);// 将自定义的过滤器添加到Spring Security 过滤器链中http.addFilterBefore(new MySecurityFilter(),UsernamePasswordAuthenticationFilter.class);}}
将该过滤器添加到 Spring Security 的过滤器链中即可生效, Spring Security 支持三种 filter 添加策略:
public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<DefaultSecurityFilterChain, HttpSecurity> implements SecurityBuilder<DefaultSecurityFilterChain>, HttpSecurityBuilder<HttpSecurity> {......// 将自定义的过滤器添加在指定过滤器之后public HttpSecurity addFilterAfter(Filter filter, Class<? extends Filter> afterFilter) {this.comparator.registerAfter(filter.getClass(), afterFilter);return this.addFilter(filter);}// 将自定义的过滤器添加在指定过滤器之前public HttpSecurity addFilterBefore(Filter filter, Class<? extends Filter> beforeFilter) {this.comparator.registerBefore(filter.getClass(), beforeFilter);return this.addFilter(filter);}// 添加一个过滤器,但必须是Spring Security自身提供的过滤器实例或其子过滤器public HttpSecurity addFilter(Filter filter) {Class<? extends Filter> filterClass = filter.getClass();if (!this.comparator.isRegistered(filterClass)) {throw new IllegalArgumentException("The Filter class " + filterClass.getName() + " does not have a registered order and cannot be added without a specified order. Consider using addFilterBefore or addFilterAfter instead.");} else {this.filters.add(filter);return this;}}// 添加一个过滤器在指定过滤器位置public HttpSecurity addFilterAt(Filter filter, Class<? extends Filter> atFilter) {this.comparator.registerAt(filter.getClass(), atFilter);return this.addFilter(filter);}......}
重启服务测试:
访问localhost:8080/login, 会跳转到 localhost:8080/login.html 页面,输入账号密码, 登录, 整个流程的日志记录如下:
非登录处理!username:adminpassword:aaaaaa非登录处理!
实战:实现图片验证码
参考:kaptcha 谷歌验证码工具 https://www.cnblogs.com/zhangyuanbo/p/11214078.html
maven 引入验证码相关包
<!-- 图片验证码相关--><dependency><groupId>com.github.penggle</groupId><artifactId>kaptcha</artifactId><version>2.3.2</version></dependency>
获取图片验证码
编写自定义的图片验证码校验过滤器:
@Beanpublic DefaultKaptcha getDDefaultKaptcha() {DefaultKaptcha dk = new DefaultKaptcha();Properties properties = new Properties();// 图片边框properties.setProperty("kaptcha.border", "yes");// 边框颜色properties.setProperty("kaptcha.border.color", "105,179,90");// 字体颜色properties.setProperty("kaptcha.textproducer.font.color", "red");// 图片宽properties.setProperty("kaptcha.image.width", "110");// 图片高properties.setProperty("kaptcha.image.height", "40");// 字体大小properties.setProperty("kaptcha.textproducer.font.size", "30");// session keyproperties.setProperty("kaptcha.session.key", "code");// 验证码长度properties.setProperty("kaptcha.textproducer.char.length", "4");// 字体properties.setProperty("kaptcha.textproducer.font.names", "宋体,楷体,微软雅黑");Config config = new Config(properties);dk.setConfig(config);return dk;}
KaptchaController.java
@Controllerpublic class KaptchaController {/*** 验证码工具*/@AutowiredDefaultKaptcha defaultKaptcha;@GetMapping("/kaptcha.jpg")public void defaultKaptcha(HttpServletRequest request, HttpServletResponse response) throws Exception {// 设置内容类型response.setContentType("image/jpeg");// 创建验证码文本String createText = defaultKaptcha.createText();// 将生成的验证码保存在session中request.getSession().setAttribute("kaptcha", createText);// 创建验证码图片BufferedImage bi = defaultKaptcha.createImage(createText);// 获取响应输出流ServletOutputStream out = response.getOutputStream();// 将图片验证码数据写入到图片输出流ImageIO.write(bi, "jpg", out);// 推送并关闭输出流out.flush();out.close();}}
当用户访问/captcha.jpg时,即可得到一张携带验证码的图片,验证码文本则被存放到 session 中,用于后续的校验。
图片验证码校验过滤器
有了图形验证码的 API 之后,就可以自定义验证码校验过滤器。
public class MyVerificationCodeFilter extends OncePerRequestFilter {@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {// 只处理登录请求if("/login".equals(request.getRequestURI())&&request.getMethod().equals(HttpMethod.POST.name())) {if(this.verificationCode(request, response)){filterChain.doFilter(request, response);}else {response.getWriter().write("verification code check failure!");}}else {filterChain.doFilter(request, response);}}private Boolean verificationCode(HttpServletRequest request,HttpServletResponse response){// 从session中获取正确的验证码HttpSession session = request.getSession();String kaptcha = (String) session.getAttribute("kaptcha");// 从参数中获取用户输入的验证码String code = request.getParameter("code");if (StringUtils.isEmpty(code)){// 清空session中的验证码,让用户重新获取session.removeAttribute("kaptcha");return false;}// 验证码校验if (!code.equals(kaptcha)){return false;}return true;}}
将MyVerificationCodeFilter添加在UsernamePasswordAuthenticationFilter之前,即在密码认证之前:
@EnableWebSecuritypublic class WebSecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {super.configure(http);// 将自定义的过滤器添加到Spring Security 过滤器链中http.addFilterBefore(new MyVerificationCodeFilter(),UsernamePasswordAuthenticationFilter.class);}
自定义带验证码的登陆页
在 static 文件夹下新建 login.html:
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Title</title></head><body><form method="post" action="/login"><input type="text" /><br/><input type="password" /><br/><div style="display: inline-block"><input type="text" /><img alt="验证码" onclick="this.src='/kaptcha.jpg'" src="/kaptcha.jpg" /><a>看不清?点击图片刷新一下</a></div></br><input type="submit" value="登录"></form></body></html>
修改WebSecurityConfig,设置自定义登录页 URL:
@EnableWebSecuritypublic class WebSecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {// super.configure(http);http.authorizeRequests().antMatchers("/kaptcha.jpg").permitAll() // 放开验证码获取的访问地址.anyRequest().authenticated().and().formLogin().loginPage("/login.html") // 自定义登录页URL.loginProcessingUrl("/login") // 自定义登陆处理请求地址.permitAll();// 将自定义的过滤器添加到Spring Security 过滤器链中http.addFilterBefore(new MyVerificationCodeFilter(),UsernamePasswordAuthenticationFilter.class);}}
重启服务, 测试执行

