Browse Source

feat(firewall): 添加SQL注入拦截器防护

- 新增 SqlInjectionInterceptor 类,拦截所有请求参数防止SQL注入攻击
- 实现对URL参数和自定义请求头的SQL注入检测
- 排除标准HTTP请求头避免误拦截
- 编写 SqlInjectionConfiguration 配置类,注册并配置拦截器路径
- 拦截所有请求但排除静态资源、Swagger文档、druid监控、错误页面和WebSocket请求
- 捕获并返回安全风险提示,拦截非法请求,提升系统安全性
SheepHy 6 days ago
parent
commit
6c451db550

+ 56 - 0
national-motion-base-core/src/main/java/org/jeecg/config/firewall/interceptor/SqlInjectionConfiguration.java

@@ -0,0 +1,56 @@
+package org.jeecg.config.firewall.interceptor;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+/**
+ * SQL注入拦截器配置
+ * 
+ * @author system
+ * @date 2025-12-05
+ */
+@Configuration
+public class SqlInjectionConfiguration implements WebMvcConfigurer {
+
+    public SqlInjectionInterceptor sqlInjectionInterceptor() {
+        return new SqlInjectionInterceptor();
+    }
+
+    @Override
+    public void addInterceptors(InterceptorRegistry registry) {
+        registry.addInterceptor(sqlInjectionInterceptor())
+                .addPathPatterns("/**")  // 拦截所有请求
+                .excludePathPatterns(
+                        // 排除静态资源
+                        "/**/*.js",
+                        "/**/*.css",
+                        "/**/*.html",
+                        "/**/*.jpg",
+                        "/**/*.png",
+                        "/**/*.gif",
+                        "/**/*.ico",
+                        "/**/*.svg",
+                        "/**/*.pdf",
+                        "/**/*.ttf",
+                        "/**/*.woff",
+                        "/**/*.woff2",
+                        "/**/*.glb",
+                        "/**/*.wasm",
+                        // 排除swagger文档
+                        "/doc.html",
+                        "/swagger-ui.html",
+                        "/swagger**/**",
+                        "/webjars/**",
+                        "/v3/**",
+                        // 排除druid监控
+                        "/druid/**",
+                        // 排除错误页面
+                        "/error",
+                        // 排除WebSocket
+                        "/websocket/**",
+                        "/newsWebsocket/**",
+                        "/vxeSocket/**"
+                );
+    }
+}

+ 153 - 0
national-motion-base-core/src/main/java/org/jeecg/config/firewall/interceptor/SqlInjectionInterceptor.java

@@ -0,0 +1,153 @@
+package org.jeecg.config.firewall.interceptor;
+
+import com.alibaba.fastjson.JSON;
+import lombok.extern.slf4j.Slf4j;
+import org.jeecg.common.api.vo.Result;
+import org.jeecg.common.exception.JeecgSqlInjectionException;
+import org.jeecg.common.util.SqlInjectionUtil;
+import org.springframework.web.servlet.HandlerInterceptor;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Enumeration;
+import java.util.Map;
+
+/**
+ * SQL注入全局拦截器
+ * 拦截所有请求参数,防止SQL注入攻击
+ * 
+ * @author system
+ * @date 2025-12-05
+ */
+@Slf4j
+public class SqlInjectionInterceptor implements HandlerInterceptor {
+
+    /**
+     * 在请求处理之前进行调用
+     */
+    @Override
+    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
+        try {
+            String requestURI = request.getRequestURI();
+            log.debug("SQL注入拦截器检查请求: {}", requestURI);
+            
+            // 检查URL参数
+            checkUrlParameters(request);
+            
+            // 检查请求头参数(排除常见的安全请求头)
+            checkHeaders(request);
+            
+            return true;
+        } catch (JeecgSqlInjectionException e) {
+            log.error("检测到SQL注入攻击! URI: {}, 错误信息: {}", request.getRequestURI(), e.getMessage());
+            handleSqlInjectionException(response, e.getMessage());
+            return false;
+        } catch (Exception e) {
+            log.error("SQL注入拦截器异常: ", e);
+            return true; // 其他异常不阻止请求继续
+        }
+    }
+
+    /**
+     * 检查URL参数
+     */
+    private void checkUrlParameters(HttpServletRequest request) {
+        Enumeration<String> parameterNames = request.getParameterNames();
+        while (parameterNames.hasMoreElements()) {
+            String paramName = parameterNames.nextElement();
+            String[] paramValues = request.getParameterValues(paramName);
+            
+            if (paramValues != null) {
+                for (String paramValue : paramValues) {
+                    if (paramValue != null && !paramValue.trim().isEmpty()) {
+                        // 进行SQL注入检测
+                        SqlInjectionUtil.filterContent(paramValue, null);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * 检查请求头(仅检查自定义请求头,排除标准HTTP请求头)
+     */
+    private void checkHeaders(HttpServletRequest request) {
+        Enumeration<String> headerNames = request.getHeaderNames();
+        while (headerNames.hasMoreElements()) {
+            String headerName = headerNames.nextElement();
+            
+            // 排除标准HTTP请求头和常见的安全请求头
+            if (isStandardHeader(headerName)) {
+                continue;
+            }
+            
+            String headerValue = request.getHeader(headerName);
+            if (headerValue != null && !headerValue.trim().isEmpty()) {
+                // 进行SQL注入检测
+                SqlInjectionUtil.filterContent(headerValue, null);
+            }
+        }
+    }
+
+    /**
+     * 判断是否为标准HTTP请求头
+     */
+    private boolean isStandardHeader(String headerName) {
+        if (headerName == null) {
+            return true;
+        }
+        
+        String lowerHeaderName = headerName.toLowerCase();
+        
+        // 标准HTTP请求头
+        return lowerHeaderName.equals("accept") 
+            || lowerHeaderName.equals("accept-encoding") 
+            || lowerHeaderName.equals("accept-language")
+            || lowerHeaderName.equals("authorization")
+            || lowerHeaderName.equals("cache-control")
+            || lowerHeaderName.equals("connection")
+            || lowerHeaderName.equals("content-type")
+            || lowerHeaderName.equals("content-length")
+            || lowerHeaderName.equals("cookie")
+            || lowerHeaderName.equals("host")
+            || lowerHeaderName.equals("origin")
+            || lowerHeaderName.equals("referer")
+            || lowerHeaderName.equals("user-agent")
+            || lowerHeaderName.equals("x-requested-with")
+            || lowerHeaderName.equals("x-forwarded-for")
+            || lowerHeaderName.equals("x-forwarded-proto")
+            || lowerHeaderName.equals("x-real-ip")
+            // Shiro和JWT相关
+            || lowerHeaderName.equals("x-access-token")
+            || lowerHeaderName.equals("x-sign")
+            || lowerHeaderName.equals("x-timestamp")
+            // CORS相关
+            || lowerHeaderName.startsWith("access-control-")
+            || lowerHeaderName.startsWith("sec-");
+    }
+
+    /**
+     * 处理SQL注入异常,返回错误信息
+     */
+    private void handleSqlInjectionException(HttpServletResponse response, String message) {
+        response.setCharacterEncoding("UTF-8");
+        response.setContentType("application/json; charset=utf-8");
+        response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
+        
+        PrintWriter writer = null;
+        try {
+            Result<Object> result = Result.error(400, "请求参数存在安全风险,禁止访问!");
+            writer = response.getWriter();
+            writer.write(JSON.toJSONString(result));
+            writer.flush();
+        } catch (IOException e) {
+            log.error("输出SQL注入错误信息失败", e);
+        } finally {
+            if (writer != null) {
+                writer.close();
+            }
+        }
+    }
+}