在 Spring Security 中自定义一个的过滤器,将其添加到 Spring Security 过滤器链的合适位置。定义一个自己的过滤器类继承 Filter 接口即可。

但是在 Spring 体系中,推荐使用 OncePerRequestFilter 来实现,它可以确保一次请求只会通过一次该过滤器(Filter 实际上并不能保证这 一点)。

  1. public class MySecurityFilter extends OncePerRequestFilter {
  2. @Override
  3. protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
  4. // 非登录请求,不处理
  5. if("/login".equals(httpServletRequest.getRequestURI())&&httpServletRequest.getMethod().equals(HttpMethod.POST.name())) {
  6. String username = httpServletRequest.getParameter("username");
  7. String password = httpServletRequest.getParameter("password");
  8. System.out.println("username:" + username);
  9. System.out.println("password:" + password);
  10. }else {
  11. System.out.println("非登录处理!");
  12. }
  13. filterChain.doFilter(httpServletRequest,httpServletResponse);
  14. }
  15. }

创建 Spring Security 配置类,继承WebSecurityConfigurerAdapter, 重写方法void configure(HttpSecurity http), 将自定义的过滤器添加到 Spring Security 过滤器链中:

  1. @EnableWebSecurity
  2. public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
  3. @Override
  4. protected void configure(HttpSecurity http) throws Exception {
  5. super.configure(http);
  6. // 将自定义的过滤器添加到Spring Security 过滤器链中
  7. http.addFilterBefore(new MySecurityFilter(),UsernamePasswordAuthenticationFilter.class);
  8. }
  9. }

将该过滤器添加到 Spring Security 的过滤器链中即可生效, Spring Security 支持三种 filter 添加策略:

  1. public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<DefaultSecurityFilterChain, HttpSecurity> implements SecurityBuilder<DefaultSecurityFilterChain>, HttpSecurityBuilder<HttpSecurity> {
  2. ......
  3. // 将自定义的过滤器添加在指定过滤器之后
  4. public HttpSecurity addFilterAfter(Filter filter, Class<? extends Filter> afterFilter) {
  5. this.comparator.registerAfter(filter.getClass(), afterFilter);
  6. return this.addFilter(filter);
  7. }
  8. // 将自定义的过滤器添加在指定过滤器之前
  9. public HttpSecurity addFilterBefore(Filter filter, Class<? extends Filter> beforeFilter) {
  10. this.comparator.registerBefore(filter.getClass(), beforeFilter);
  11. return this.addFilter(filter);
  12. }
  13. // 添加一个过滤器,但必须是Spring Security自身提供的过滤器实例或其子过滤器
  14. public HttpSecurity addFilter(Filter filter) {
  15. Class<? extends Filter> filterClass = filter.getClass();
  16. if (!this.comparator.isRegistered(filterClass)) {
  17. 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.");
  18. } else {
  19. this.filters.add(filter);
  20. return this;
  21. }
  22. }
  23. // 添加一个过滤器在指定过滤器位置
  24. public HttpSecurity addFilterAt(Filter filter, Class<? extends Filter> atFilter) {
  25. this.comparator.registerAt(filter.getClass(), atFilter);
  26. return this.addFilter(filter);
  27. }
  28. ......
  29. }

重启服务测试:
访问localhost:8080/login, 会跳转到 localhost:8080/login.html 页面,输入账号密码, 登录, 整个流程的日志记录如下:

  1. 非登录处理!
  2. username:admin
  3. password:aaaaaa
  4. 非登录处理!

实战:实现图片验证码

参考:kaptcha 谷歌验证码工具 https://www.cnblogs.com/zhangyuanbo/p/11214078.html

maven 引入验证码相关包

  1. <!-- 图片验证码相关-->
  2. <dependency>
  3. <groupId>com.github.penggle</groupId>
  4. <artifactId>kaptcha</artifactId>
  5. <version>2.3.2</version>
  6. </dependency>

获取图片验证码

编写自定义的图片验证码校验过滤器:

  1. @Bean
  2. public DefaultKaptcha getDDefaultKaptcha() {
  3. DefaultKaptcha dk = new DefaultKaptcha();
  4. Properties properties = new Properties();
  5. // 图片边框
  6. properties.setProperty("kaptcha.border", "yes");
  7. // 边框颜色
  8. properties.setProperty("kaptcha.border.color", "105,179,90");
  9. // 字体颜色
  10. properties.setProperty("kaptcha.textproducer.font.color", "red");
  11. // 图片宽
  12. properties.setProperty("kaptcha.image.width", "110");
  13. // 图片高
  14. properties.setProperty("kaptcha.image.height", "40");
  15. // 字体大小
  16. properties.setProperty("kaptcha.textproducer.font.size", "30");
  17. // session key
  18. properties.setProperty("kaptcha.session.key", "code");
  19. // 验证码长度
  20. properties.setProperty("kaptcha.textproducer.char.length", "4");
  21. // 字体
  22. properties.setProperty("kaptcha.textproducer.font.names", "宋体,楷体,微软雅黑");
  23. Config config = new Config(properties);
  24. dk.setConfig(config);
  25. return dk;
  26. }

KaptchaController.java

  1. @Controller
  2. public class KaptchaController {
  3. /**
  4. * 验证码工具
  5. */
  6. @Autowired
  7. DefaultKaptcha defaultKaptcha;
  8. @GetMapping("/kaptcha.jpg")
  9. public void defaultKaptcha(HttpServletRequest request, HttpServletResponse response) throws Exception {
  10. // 设置内容类型
  11. response.setContentType("image/jpeg");
  12. // 创建验证码文本
  13. String createText = defaultKaptcha.createText();
  14. // 将生成的验证码保存在session中
  15. request.getSession().setAttribute("kaptcha", createText);
  16. // 创建验证码图片
  17. BufferedImage bi = defaultKaptcha.createImage(createText);
  18. // 获取响应输出流
  19. ServletOutputStream out = response.getOutputStream();
  20. // 将图片验证码数据写入到图片输出流
  21. ImageIO.write(bi, "jpg", out);
  22. // 推送并关闭输出流
  23. out.flush();
  24. out.close();
  25. }
  26. }

当用户访问/captcha.jpg时,即可得到一张携带验证码的图片,验证码文本则被存放到 session 中,用于后续的校验。

图片验证码校验过滤器

有了图形验证码的 API 之后,就可以自定义验证码校验过滤器。

  1. public class MyVerificationCodeFilter extends OncePerRequestFilter {
  2. @Override
  3. protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
  4. // 只处理登录请求
  5. if("/login".equals(request.getRequestURI())&&request.getMethod().equals(HttpMethod.POST.name())) {
  6. if(this.verificationCode(request, response)){
  7. filterChain.doFilter(request, response);
  8. }else {
  9. response.getWriter().write("verification code check failure!");
  10. }
  11. }else {
  12. filterChain.doFilter(request, response);
  13. }
  14. }
  15. private Boolean verificationCode(HttpServletRequest request,HttpServletResponse response){
  16. // 从session中获取正确的验证码
  17. HttpSession session = request.getSession();
  18. String kaptcha = (String) session.getAttribute("kaptcha");
  19. // 从参数中获取用户输入的验证码
  20. String code = request.getParameter("code");
  21. if (StringUtils.isEmpty(code)){
  22. // 清空session中的验证码,让用户重新获取
  23. session.removeAttribute("kaptcha");
  24. return false;
  25. }
  26. // 验证码校验
  27. if (!code.equals(kaptcha)){
  28. return false;
  29. }
  30. return true;
  31. }
  32. }

MyVerificationCodeFilter添加在UsernamePasswordAuthenticationFilter之前,即在密码认证之前:

  1. @EnableWebSecurity
  2. public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
  3. @Override
  4. protected void configure(HttpSecurity http) throws Exception {
  5. super.configure(http);
  6. // 将自定义的过滤器添加到Spring Security 过滤器链中
  7. http
  8. .addFilterBefore(new MyVerificationCodeFilter(),UsernamePasswordAuthenticationFilter.class);
  9. }

自定义带验证码的登陆页

在 static 文件夹下新建 login.html:

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Title</title>
  6. </head>
  7. <body>
  8. <form method="post" action="/login">
  9. <input type="text" /><br/>
  10. <input type="password" /><br/>
  11. <div style="display: inline-block">
  12. <input type="text" />
  13. <img alt="验证码" onclick="this.src='/kaptcha.jpg'" src="/kaptcha.jpg" />
  14. <a>看不清?点击图片刷新一下</a>
  15. </div>
  16. </br>
  17. <input type="submit" value="登录">
  18. </form>
  19. </body>
  20. </html>

修改WebSecurityConfig,设置自定义登录页 URL:

  1. @EnableWebSecurity
  2. public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
  3. @Override
  4. protected void configure(HttpSecurity http) throws Exception {
  5. // super.configure(http);
  6. http
  7. .authorizeRequests()
  8. .antMatchers("/kaptcha.jpg").permitAll() // 放开验证码获取的访问地址
  9. .anyRequest()
  10. .authenticated()
  11. .and()
  12. .formLogin()
  13. .loginPage("/login.html") // 自定义登录页URL
  14. .loginProcessingUrl("/login") // 自定义登陆处理请求地址
  15. .permitAll();
  16. // 将自定义的过滤器添加到Spring Security 过滤器链中
  17. http
  18. .addFilterBefore(new MyVerificationCodeFilter(),UsernamePasswordAuthenticationFilter.class);
  19. }
  20. }

重启服务, 测试执行

(五)Spring Security自定义过滤器 - 图1