(五)Spring Security自定义过滤器

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

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

public class MySecurityFilter extends OncePerRequestFilter {
    @Override
    protected 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 过滤器链中:

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected 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:admin
password: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>

获取图片验证码

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

  @Bean
    public 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 key
        properties.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

@Controller
public class KaptchaController {

    /**
     * 验证码工具
     */
    @Autowired
    DefaultKaptcha 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 {
    @Override
    protected 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之前,即在密码认证之前:

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected 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" name="username"/><br/>
    <input type="password" name="password"/><br/>
    <div style="display: inline-block">
        <input type="text" name="code"  required placeholder="验证码" />
        <img alt="验证码" onclick="this.src='/kaptcha.jpg'" src="/kaptcha.jpg" />
        <a>看不清?点击图片刷新一下</a>
    </div>
    </br>
    <input type="submit" value="登录">
</form>
</body>
</html>

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

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected 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);
    }
}

重启服务,测试执行

在这里插入图片描述

posted @ 2022-10-30 23:22  寒小韩  阅读(1927)  评论(0编辑  收藏  举报