• Ruo-Yi 前后端分离防止XSS攻击和自定义可以重复读取InputStream流


    Ruo-Yi 前后端分离防止XSS攻击和自定义可以重复读取InputStream流

    防止XSS攻击分析

    1、什么是xss攻击

    ​ **XSS 即(Cross Site Scripting)中文名称为:跨站脚本攻击。**XSS的重点不在于跨站点,而在于脚本的执行。那么XSS的原理是:

    ​ 恶意攻击者在web页面中会插入一些恶意的script代码。当用户浏览该页面的时候,那么嵌入到web页面中script代码会执行,因此会达到恶意攻击用户的目的。

    ​ 那么XSS攻击最主要有如下分类:反射型、存储型、及 DOM-based型。 反射性和DOM-baseed型可以归类为非持久性XSS攻击。存储型可以归类为持久性XSS攻击。

    2、Ruo-Yi 实现的步骤以及代码分析
    1、配置文件
    xss: 
      # 过滤开关
      enabled: true
      # 排除链接(多个用逗号分隔)
      excludes: /system/notice
      # 匹配链接
      urlPatterns: /system/*,/monitor/*,/tool/*
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    # 是否开启xss
    - enabled:true开启;false不开启
    # excludes
    	xss过滤器不经过的请求路径
    # urlPatterns
    	xss过滤器经过的请求路径
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    2、自定义xss攻击的过滤器
    package com.ruoyi.common.filter;
    
    import com.ruoyi.common.enums.HttpMethod;
    import com.ruoyi.common.utils.StringUtils;
    
    import javax.servlet.*;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * 防止XSS攻击的过滤器
     *      防止 xss 攻击
     *      如果请求是 GET、DELETE、或者是在 excludes集合里面,直接放行,不经过 XssHttpServletRequestWrapper
     * 
     * @author ruoyi
     */
    public class XssFilter implements Filter
    {
        /**
         * 排除链接
         *      this.init()方法会将需要排序的路径写入这个 List 集合
         */
        public List<String> excludes = new ArrayList<>();
    
        /**
         * 最先走这个 init() 方法,然后才会走 doFilter() 方法
         * @param filterConfig 这个是我们若依定义的类
         * @throws ServletException
         */
        @Override
        public void init(FilterConfig filterConfig) throws ServletException
        {
            String tempExcludes = filterConfig.getInitParameter("excludes");
            if (StringUtils.isNotEmpty(tempExcludes))
            {
                String[] url = tempExcludes.split(",");
                for (int i = 0; url != null && i < url.length; i++)
                {
                    excludes.add(url[i]);
                }
            }
        }
    
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
                throws IOException, ServletException
        {
            HttpServletRequest req = (HttpServletRequest) request; // 获取请求对象
            HttpServletResponse resp = (HttpServletResponse) response; // 获取响应对象
            if (handleExcludeURL(req, resp))
            {
                // 如果请求是 GET、DELETE、或者是在 excludes集合里面,直接放行
                chain.doFilter(request, response);
                return;
            }
            // 自定义 XssHttpServletRequestWrapper 类 ,里面进行 防止 xss 攻击
            XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper((HttpServletRequest) request);
            chain.doFilter(xssRequest, response);
        }
    
        // 如果请求是 GET、DELETE、或者是在 excludes集合里面,返回 true
        // 其他返回 false
        private boolean handleExcludeURL(HttpServletRequest request, HttpServletResponse response)
        {
            String url = request.getServletPath();
            String method = request.getMethod(); // 请求的方式:GET
            // GET DELETE 不过滤
            if (method == null || HttpMethod.GET.matches(method) || HttpMethod.DELETE.matches(method))
            {
                return true;
            }
            return StringUtils.matches(url, excludes);
        }
    
        @Override
        public void destroy()
        {
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    package com.ruoyi.common.filter;
    
    import com.ruoyi.common.utils.StringUtils;
    import com.ruoyi.common.utils.html.EscapeUtil;
    import org.apache.commons.io.IOUtils;
    import org.springframework.http.HttpHeaders;
    import org.springframework.http.MediaType;
    
    import javax.servlet.ReadListener;
    import javax.servlet.ServletInputStream;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletRequestWrapper;
    import java.io.ByteArrayInputStream;
    import java.io.IOException;
    
    /**
     * XSS过滤处理
     * 
     * @author ruoyi
     */
    public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper
    {
        /**
         * @param request
         */
        public XssHttpServletRequestWrapper(HttpServletRequest request)
        {
            super(request);
        }
    
        @Override
        public String[] getParameterValues(String name)
        {
            // 获得有 name 指定的参数的所有值
            String[] values = super.getParameterValues(name);
            if (values != null)
            {
                // 获取到的 values 不为空
                int length = values.length;
                String[] escapseValues = new String[length];
                for (int i = 0; i < length; i++)
                {
                    // 防xss攻击和过滤前后空格 具体解决 xss 攻击的其实是 EscapeUtil 工具类
                    // 防xss攻击和过滤前后空格
                    escapseValues[i] = EscapeUtil.clean(values[i]).trim(); // EscapeUtil.clean:清除所有HTML标签,但是不删除标签内的内容
                }
                return escapseValues;
            }
            // 调用系统里面的 getParameterValues
            return super.getParameterValues(name);
        }
    
        /**
         * 有可能提交的是图片形式的
         */
        @Override
        public ServletInputStream getInputStream() throws IOException
        {
            // 非json类型,直接返回
            if (!isJsonRequest())
            {
                return super.getInputStream();
            }
    
            // 为空,直接返回
            String json = IOUtils.toString(super.getInputStream(), "utf-8");
            if (StringUtils.isEmpty(json))
            {
                return super.getInputStream();
            }
    
            // xss过滤
            json = EscapeUtil.clean(json).trim();
            byte[] jsonBytes = json.getBytes("utf-8");
            final ByteArrayInputStream bis = new ByteArrayInputStream(jsonBytes);
            return new ServletInputStream()
            {
                @Override
                public boolean isFinished()
                {
                    return true;
                }
    
                @Override
                public boolean isReady()
                {
                    return true;
                }
    
                @Override
                public int available() throws IOException
                {
                    return jsonBytes.length;
                }
    
                @Override
                public void setReadListener(ReadListener readListener)
                {
                }
    
                @Override
                public int read() throws IOException
                {
                    return bis.read();
                }
            };
        }
    
        /**
         * 是否是Json请求
         * 
         * @param request
         */
        public boolean isJsonRequest()
        {
            // CONTENT_TYPE = "Content-Type";
            String header = super.getHeader(HttpHeaders.CONTENT_TYPE);
            // APPLICATION_JSON_VALUE = "application/json";
            // 不分大小写检查字符序列是否以指定的前缀开始【application/json】
            return StringUtils.startsWithIgnoreCase(header, MediaType.APPLICATION_JSON_VALUE);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    3、将xss过滤器配置到系统中
    package com.ruoyi.framework.config;
    
    import com.ruoyi.common.filter.RepeatableFilter;
    import com.ruoyi.common.filter.XssFilter;
    import com.ruoyi.common.utils.StringUtils;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
    import org.springframework.boot.web.servlet.FilterRegistrationBean;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    import javax.servlet.DispatcherType;
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * Filter配置
     *      为了给 项目添加一些 自定义过滤器,也可以设置 filter 的排序值
     *
     *      Spring 提供了FilterRegistrationBean类,此类提供setOrder方法,可以为filter设置排序值,让spring在注册web filter之前排序后再依次注册。
     * @author ruoyi
     */
    @Configuration
    public class FilterConfig
    {
        //   # 排除链接(多个用逗号分隔) excludes: /system/notice
        @Value("${xss.excludes}")
        private String excludes;
    
        //   # 匹配链接  urlPatterns: /system/*,/monitor/*,/tool/*
        @Value("${xss.urlPatterns}")
        private String urlPatterns;
    
        // 添加 XssFilter 过滤器
        @SuppressWarnings({ "rawtypes", "unchecked" })
        @Bean
        /**
         * 如果 application.yml 文件中的 xss.enabled 值和 havingValue 值相同的话 就返回 true,即下面配置类生效
         * 不相等的话 下面的类就不生效,即 spring 池里面没有 FilterRegistrationBean 对象
         */
        @ConditionalOnProperty(value = "xss.enabled", havingValue = "true")
        public FilterRegistrationBean xssFilterRegistration()
        {
            FilterRegistrationBean registration = new FilterRegistrationBean();
            registration.setDispatcherTypes(DispatcherType.REQUEST);
            registration.setFilter(new XssFilter());
            // 添加需要 XssFilter 过滤的路径
            registration.addUrlPatterns(StringUtils.split(urlPatterns, ","));
            // 设置过滤器名称
            registration.setName("xssFilter");
            // 设置 添加过滤器的 级别,越低越先执行
            registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE);
            Map<String, String> initParameters = new HashMap<String, String>();
            // 添加不需要 XssFilter 拦截的路径,但是这些路径其实还是会先走  XssFilter 过滤器,只是在这个 过滤器里面会放行掉
            initParameters.put("excludes", excludes);
            registration.setInitParameters(initParameters);
            return registration;
        }
    
        // 添加 RepeatableFilter 过滤器
        @SuppressWarnings({ "rawtypes", "unchecked" })
        @Bean
        public FilterRegistrationBean someFilterRegistration()
        {
            FilterRegistrationBean registration = new FilterRegistrationBean();
            registration.setFilter(new RepeatableFilter());
            // 添加 所有路径都需要走 这个过滤器
            registration.addUrlPatterns("/*");
            registration.setName("repeatableFilter");
            // 最低级别 最后执行的 过滤器
            registration.setOrder(FilterRegistrationBean.LOWEST_PRECEDENCE);
            return registration;
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75

    可以给系统添加过滤器,也可以设置过滤器的执行顺序

    4、总结

    1、在若依基础上开发的话,如果我们不想使用xss攻击过滤器的话,只需要将 application.yml文件里面,改为false

    # 防止XSS攻击
    xss: 
      # 过滤开关
      enabled: false
    
    • 1
    • 2
    • 3
    • 4

    2、如果只想某些接口不进行xss攻击过滤器的话,只需要在excludes里面加上想要路径即可:

    # 防止XSS攻击
    xss: 
      # 过滤开关
      enabled: true
      # 排除链接(多个用逗号分隔)
      excludes: /system/notice
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    5、拓展:使用mica-xss实现防止xss攻击

    参考:https://blog.csdn.net/HDesigner/article/details/121246322?spm=1001.2101.3001.6650.4&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-4-121246322-blog-126586638.t5_landing_title_tags_v3&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-4-121246322-blog-126586638.t5_landing_title_tags_v3&utm_relevant_index=5

    自定义可以重复读取InputStream流

    1、问题

    ​ 系统为了提高项目安全性,拦截非法访问,要给项目增加了一个过滤器,拦截所有的请求,校验是否有不安全因素。 这个过程就遇到了一个问题:ServletRequest 的 getReader() 和 getInputStream() 两个方法只能被调用一次,而且不能两个都调用。那么如果 Filter 中调用了一次,在 Controller 里面就不能再调用了

    2、解决思路

    ​ 解决问题思路:先将 RequestBody 保存为一个byte数组,然后通过Servlet自带的 HttpServletRequestWrapper 类覆盖getReader()和getInputStream()方法,使流从保存的byte数组读取。然后在Filter中将 ServletRequest 替换为 RepeatedlyRequestWrapper。

    3、实现方法

    1、创建过滤器

    package com.ruoyi.common.filter;
    
    import com.ruoyi.common.utils.StringUtils;
    import org.springframework.http.MediaType;
    
    import javax.servlet.*;
    import javax.servlet.http.HttpServletRequest;
    import java.io.IOException;
    
    /**
     * Repeatable 过滤器
     * 
     * @author ruoyi
     */
    public class RepeatableFilter implements Filter
    {
        @Override
        public void init(FilterConfig filterConfig) throws ServletException
        {
    
        }
    
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
                throws IOException, ServletException
        {
            ServletRequest requestWrapper = null;
            // 判断请求是否属于 HttpServletRequest,并且 Content-type 是否是 "application/json"
            if (request instanceof HttpServletRequest
                    && StringUtils.startsWithIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE))
            {
                // 判断结果:HttpServletRequest 请求,Content-type 是 "application/json"
                // 创建包装类 RepeatedlyRequestWrapper 封装原生 HttpServletRequest 请求
    
                // Filter 中将 ServletRequest 替换为 RepeatedlyRequestWrapper
                requestWrapper = new RepeatedlyRequestWrapper((HttpServletRequest) request, response);
            }
            if (null == requestWrapper)
            {
                // 如果为空 --> 使用原生 HttpServletRequest 请求
                chain.doFilter(request, response);
            }
            else
            {
                // 如果不为空 -->  使用包装类 RepeatedlyRequestWrapper
                chain.doFilter(requestWrapper, response);
            }
        }
    
        @Override
        public void destroy()
        {
    
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    package com.ruoyi.common.filter;
    
    import com.ruoyi.common.constant.Constants;
    import com.ruoyi.common.utils.http.HttpHelper;
    
    import javax.servlet.ReadListener;
    import javax.servlet.ServletInputStream;
    import javax.servlet.ServletResponse;
    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;
    
    /**
     * 构建可重复读取inputStream的request
     *
     *  问题:系统为了提高项目安全性,拦截非法访问,要给项目增加了一个过滤器,拦截所有的请求,校验是否有不安全因素。
     *      这个过程就遇到了一个问题:ServletRequest 的 getReader() 和 getInputStream() 两个方法只能被调用一次,而且不能两个都调用。
     *      那么如果 Filter 中调用了一次,在 Controller 里面就不能再调用了
     *
     *  解决问题思路:先将 RequestBody 保存为一个byte数组,然后通过Servlet自带的 HttpServletRequestWrapper 类覆盖getReader()和getInputStream()方法,
     *      使流从保存的byte数组读取。然后在Filter中将 ServletRequest 替换为 RepeatedlyRequestWrapper。【】
     *
     * @author ruoyi
     */
    public class RepeatedlyRequestWrapper extends HttpServletRequestWrapper
    {
        /**
         * ServletRequest 类里面的 getInputStream() 和 getReader() 两个方法都注明方法只能被调用一次,由于RequestBody是流的形式读取,
         * 那么流读了一次就没有了,所以只能被调用一次。既然是因为流只能读一次的原因,那么只要将流的内容保存下来,就可以实现反复读取了。
         * byte数组允许被多次读取,而不会丢失内容。下面使用byte数组将流的内容保存下来。
         */
        // 用于保存流的 byte[]
        private final byte[] body;
    
        public RepeatedlyRequestWrapper(HttpServletRequest request, ServletResponse response) throws IOException
        {
            super(request);
            request.setCharacterEncoding(Constants.UTF8);
            response.setCharacterEncoding(Constants.UTF8);
    
            // 读取流中的数据 并保存到数组 body
            body = HttpHelper.getBodyString(request).getBytes(Constants.UTF8);
        }
    
        @Override
        public BufferedReader getReader() throws IOException
        {
            return new BufferedReader(new InputStreamReader(getInputStream()));
        }
    
        @Override
        public ServletInputStream getInputStream() throws IOException
        {
            // 从 body 中创建新的流
            final ByteArrayInputStream bais = new ByteArrayInputStream(body);
            return new ServletInputStream()
            {
                @Override
                public int read() throws IOException
                {
                    return bais.read();
                }
    
                @Override
                public int available() throws IOException
                {
                    return body.length;
                }
    
                @Override
                public boolean isFinished()
                {
                    return false;
                }
    
                @Override
                public boolean isReady()
                {
                    return false;
                }
    
                @Override
                public void setReadListener(ReadListener readListener)
                {
    
                }
            };
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93

    2、将过滤器配置到系统中

    package com.ruoyi.framework.config;
    
    import com.ruoyi.common.filter.RepeatableFilter;
    import com.ruoyi.common.filter.XssFilter;
    import com.ruoyi.common.utils.StringUtils;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
    import org.springframework.boot.web.servlet.FilterRegistrationBean;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    import javax.servlet.DispatcherType;
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * Filter配置
     *      为了给 项目添加一些 自定义过滤器,也可以设置 filter 的排序值
     *
     *      Spring 提供了FilterRegistrationBean类,此类提供setOrder方法,可以为filter设置排序值,让spring在注册web filter之前排序后再依次注册。
     * @author ruoyi
     */
    @Configuration
    public class FilterConfig
    {
        // 添加 RepeatableFilter 过滤器
        @SuppressWarnings({ "rawtypes", "unchecked" })
        @Bean
        public FilterRegistrationBean someFilterRegistration()
        {
            FilterRegistrationBean registration = new FilterRegistrationBean();
            registration.setFilter(new RepeatableFilter());
            // 添加 所有路径都需要走 这个过滤器
            registration.addUrlPatterns("/*");
            registration.setName("repeatableFilter");
            // 最低级别 最后执行的 过滤器
            registration.setOrder(FilterRegistrationBean.LOWEST_PRECEDENCE);
            return registration;
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    拦截(Interceptor)和过滤器(Filter)的执行顺序

    先进入过滤器后进入拦截器,先出拦截器后出过滤器

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6gQ9pheo-1666346197554)(Ruo-Yi 前后端分离防止XSS攻击.assets/image-20221021175511020.png)]

  • 相关阅读:
    one-model引擎:私域营销推荐自动化解决方案【转载】
    第十三届蓝桥杯省赛C++ C组《全题目+题解》
    这几个拍图读字软件你见过吗?附赠使用方法
    对于双列集合map的学习
    Windows Nginx 服务器部署(保姆级)
    【Java 基础篇】Java网络编程实战:P2P文件共享详解
    从各大论坛收集整理的八股文手册,肝完横躺95%的Java面试岗位
    二叉树中和为某一值的路径(一)(二)(三)(剑指offer)
    Linux C语言开发-D10控制语句if
    第十二章:泛型(Generic)
  • 原文地址:https://blog.csdn.net/H_Q_Li/article/details/127451218