springboot中常用的过滤器和拦截器编写

过滤器和拦截器的区别

在Spring Boot中,过滤器(Filter)和拦截器(Interceptor)都是处理HTTP请求的重要组成部分。尽管他们在功能上有些许相似,但他们的使用场景和实现方式有所不同。下面是这两者之间的主要区别:

  1. 所在层次:过滤器是Servlet规范中的一部分,由Servlet容器管理。拦截器是Spring MVC的一部分,由DispatcherServlet管理。这意味着过滤器在拦截器之前执行。

  2. 拦截范围:过滤器可以拦截所有的请求,包括静态资源,如HTML,JS,CSS等。而拦截器只拦截那些被Spring的DispatcherServlet处理的请求。

  3. 参数传递:过滤器只能使用ServletRequest和ServletResponse对象。而拦截器可以访问到更具体的请求和响应对象(例如,HttpServletRequest和HttpServletResponse),还可以通过HandlerMethod获取处理请求的方法和类的信息。

  4. 功能:过滤器主要用于处理通用的请求处理,例如:设置请求编码,过滤非法字符,防止SQL注入等。拦截器主要用于处理业务逻辑,例如:登录检查,权限验证,程序执行时间等。

  5. 底层实现:过滤器的实现基于回调函数。而拦截器(代理模式)的实现基于反射

引入依赖

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.16</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.fastjson2</groupId>
            <artifactId>fastjson2</artifactId>
            <version>2.0.27</version>
        </dependency>
    </dependencies>

一、解决请求体不能反复读取并编写拦截器记录日志

自定义wrapper复制请求流, 如果不重写会报 java.lang.IllegalStateException: getInputStream() has already been called for this request 异常, 原因是request请求流不能重复读取。

import lombok.Getter;
import org.springframework.util.StreamUtils;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;

public class MyHttpServletRequestWrapper extends HttpServletRequestWrapper {
    private final byte[] body;
    /** 复制请求body */
    @Getter
    private final String bodyString;

    public MyHttpServletRequestWrapper (HttpServletRequest request) throws IOException {
        super(request);
        this.bodyString = StreamUtils.copyToString(request.getInputStream(), StandardCharsets.UTF_8);
        this.body = this.bodyString.getBytes(StandardCharsets.UTF_8);
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        //返回body的流信息即可
        try(final ByteArrayInputStream bais = new ByteArrayInputStream(this.body)){
            return new ServletInputStream() {
                @Override
                public boolean isFinished() {
                    return false;
                }

                @Override
                public boolean isReady() {
                    return false;
                }

                @Override
                public void setReadListener(ReadListener readListener) {
                }
                @Override
                public int read() {
                    return bais.read();
                }
            };
        }
    }

    @Override
    public BufferedReader getReader() throws IOException{
        return new BufferedReader(new InputStreamReader(this.getInputStream()));
    }

}

自定义filter, 必须将自定义的wrapper通过过滤器传下去, 不传不会调用重写后的getInputStream()和getReader()方法

import com.example.filter.demo.wrapper.MyHttpServletRequestWrapper;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Objects;
import java.util.Optional;

@Component
@WebFilter(filterName = "RewriteRequestFilter", urlPatterns = "/*")
@Order(0)
public class RewriteRequestFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        //文件上传类型 不需要处理,否则会报java.nio.charset.MalformedInputException: Input length = 1异常
        if (Objects.isNull(request) || Optional.ofNullable(request.getContentType()).orElse(StringUtils.EMPTY).startsWith("multipart/")) {
            chain.doFilter(request, response);
            return;
        }
        //自定义wrapper 处理流,必须在过滤器中处理,然后通过FilterChain传下去, 否则重写后的getInputStream()方法不会被调用
        MyHttpServletRequestWrapper requestWrapper = new MyHttpServletRequestWrapper((HttpServletRequest)request);
        chain.doFilter(requestWrapper,response);
    }
}

 自定义日志类

import lombok.Data;

import java.util.Map;

@Data
public class LoggerInfoDTO {

    /**
     *
     */
    private String clientIp;

    /**
     *
     */
    private String url;

    /**
     *
     */
    private String type;

    /**
     * 请求方式GET、POST、PUT、DELETE
     */
    private String method;

    /**
     * URL后的参数
     */
    private Map<String, String[]> requestParam;

    /**
     * restful风格的路径参数
     */
    private Map<String, Object> pathVariable;

    /**
     * 请求体信息
     */
    private String requestBody;

    private String sessionId;
    
}

自定义interceptor拦截器, 判断如果是自己的wrapper, 从wrapper中获取请求参数。只能在拦截器中获取请求参数,

1.如果在自定义的Filter中获取请求参数, restful风格的请求参数无法获取。

2.如果在AOP中获取请求参数, 获取的是序列化后的请求参数(基本类型默认值也会被获取)

import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.json.JSONUtil;
import com.example.filter.demo.dto.LoggerInfoDTO;
import com.example.filter.demo.wrapper.MyHttpServletRequestWrapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.HandlerMapping;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Objects;

@Slf4j
public class LoggerInterceptor implements HandlerInterceptor {

        private static final ThreadLocal<LoggerInfoDTO> threadLocal = new ThreadLocal<>();

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)  throws Exception {

        LoggerInfoDTO loggerInfo = new LoggerInfoDTO();
        // 设置请求方法,GET,POST...
        loggerInfo.setMethod(request.getMethod());
        // 设置请求sessionId
        loggerInfo.setSessionId(request.getRequestedSessionId());
        // 设置请求路径
        loggerInfo.setUrl(request.getRequestURI());
        // 设置@RequestParam注解的参数
        loggerInfo.setRequestParam(request.getParameterMap());
        // 设置restful请求参数,必须在interceptor拦截其中才能这样获取到restful参数
        Object attribute = request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
        if(Objects.nonNull(attribute)){
            loggerInfo.setPathVariable(JSONUtil.parseObj(attribute));
        }
        //设置客户端ip
        loggerInfo.setClientIp(ServletUtil.getClientIP(request));

        //如果是自定义wrapper, 从自定义wrapper中获取请求参数, 必须在interceptor拦截器中处理, 否则restful风格的请求参数获取不到。
        if(request instanceof MyHttpServletRequestWrapper){
            MyHttpServletRequestWrapper myHttpServletRequestWrapper = (MyHttpServletRequestWrapper) request;
            String bodyString = myHttpServletRequestWrapper.getBodyString();
            if(StringUtils.isNoneBlank(bodyString)){
                loggerInfo.setRequestBody(bodyString);
            }

        }
        //放到ThreadLocal中, 这里可以根据自己的项目业务处理
        log.info("loggerInfo : {}", JSONUtil.toJsonPrettyStr(loggerInfo));
        threadLocal.set(loggerInfo);
        return true;
    }

}

 编写拦截器配置类

import com.example.filter.demo.interceptor.LoggerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loggerInterceptor())
                // 拦截规则 ,拦截那些路径
                .addPathPatterns("/**")
                // 那些路径不拦截
                .excludePathPatterns("/user/login","/error");
    }

    @Bean
    public LoggerInterceptor loggerInterceptor() {
        return new LoggerInterceptor();
    }
}

二、编写防止xss攻击的过滤器

增强MyHttpServletRequestWrapper

    /**
     * @param request
     */
    public XssHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        String json = StreamUtils.copyToString(request.getInputStream(), StandardCharsets.UTF_8);
        JSONObject jsonObject = JSONUtil.parseObj(json);
        // xss过滤,HtmlUtil.filter的validateEntities方法会将双引号转码,所以不能将json整个过滤
        jsonObject.forEach((key,value) -> {
            value = HtmlUtil.filter(String.valueOf(value)).trim();
            jsonObject.set(key, value);
        });
        this.bodyString = JSONUtil.toJsonStr(jsonObject);
        this.body = this.bodyString.getBytes(StandardCharsets.UTF_8);
    }

    /**
     * 重写获取参数进行转义
     * @param name
     * @return
     */
    @Override
    public String[] getParameterValues(String name) {
        String[] values = super.getParameterValues(name);
        if (values != null) {
            int length = values.length;
            String[] escapesValues = new String[length];
            for (int i = 0; i < length; i++) {
                // 防xss攻击和过滤前后空格
                escapesValues[i] = HtmlUtil.filter(values[i]).trim();
            }
            return escapesValues;
        }
        return super.getParameterValues(name);
    }

三、使用拦截器+注解进行鉴权

编写注解

import java.lang.annotation.*;

@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface AuthCheck {
    /**
     * 授权编码
     * @return
     */
    String[] authCode();
}
import java.lang.annotation.*;

@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface NoAuth {
}

编写拦截器

import com.wulei.interceptor.demo.annotation.AuthCheck;
import com.wulei.interceptor.demo.annotation.NoAuth;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;

@Component
public class AuthCheckInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }
        // HandlerMethod 提供对方法参数、方法返回值、方法注释等的便捷访问。
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        // Method 提供有关类或接口上的单个方法的信息和访问。
        Method method = handlerMethod.getMethod();


        if (method.getAnnotation(NoAuth.class) != null // 获取方法上注解
                // 获取类上注解
            || handlerMethod.getBeanType().getAnnotation(NoAuth.class) != null) {
            return true;
        }

        // TODO 获取用户角色和权限功能点

        // 判断注解功能点是否正确
        // 获取方法上注解
        AuthCheck authCheck = method.getAnnotation(AuthCheck.class);
        if (authCheck == null) {
            // 获取类上注解
            authCheck = handlerMethod.getBeanType().getAnnotation(AuthCheck.class);
        }

        String[] authCode = authCheck != null ? authCheck.authCode() : null;

        if (authCode == null || authCode.length == 0) {
            return false;
        }

        // TODO 判断用户功能点权限

        return true;
    }
}

猜你喜欢

转载自blog.csdn.net/m0_68705273/article/details/130086002