瀏覽代碼

完成一部分 xss 的功能,准备先午睡~~~

YunaiV 4 年之前
父節點
當前提交
8605cc35c9

+ 5 - 0
pom.xml

@@ -192,6 +192,11 @@
             <artifactId>hutool-captcha</artifactId>
             <version>${hutool.version}</version>
         </dependency>
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-http</artifactId>
+            <version>${hutool.version}</version>
+        </dependency>
 
         <dependency>
             <groupId>com.alibaba</groupId>

+ 0 - 35
ruoyi-common/src/main/java/com/ruoyi/common/config/ResourcesConfig.java

@@ -1,35 +0,0 @@
-package com.ruoyi.framework.config;
-
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.web.cors.CorsConfiguration;
-import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
-import org.springframework.web.filter.CorsFilter;
-import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
-import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
-import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
-import com.ruoyi.common.config.RuoYiConfig;
-import com.ruoyi.common.constant.Constants;
-import com.ruoyi.framework.interceptor.RepeatSubmitInterceptor;
-
-/**
- * 通用配置
- *
- * @author ruoyi
- */
-@Configuration
-public class ResourcesConfig implements WebMvcConfigurer {
-
-    @Autowired
-    private RepeatSubmitInterceptor repeatSubmitInterceptor;
-
-    /**
-     * 自定义拦截规则
-     */
-    @Override
-    public void addInterceptors(InterceptorRegistry registry) {
-        registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("/**");
-    }
-
-}

+ 0 - 30
ruoyi-common/src/main/java/com/ruoyi/common/config/ServerConfig.java

@@ -1,30 +0,0 @@
-package com.ruoyi.framework.config;
-
-import javax.servlet.http.HttpServletRequest;
-
-import org.springframework.stereotype.Component;
-import com.ruoyi.common.utils.ServletUtils;
-
-/**
- * 服务相关配置
- *
- * @author ruoyi
- */
-@Component
-public class ServerConfig {
-    /**
-     * 获取完整的请求路径,包括:域名,端口,上下文访问路径
-     *
-     * @return 服务地址
-     */
-    public String getUrl() {
-        HttpServletRequest request = ServletUtils.getRequest();
-        return getDomain(request);
-    }
-
-    public static String getDomain(HttpServletRequest request) {
-        StringBuffer url = request.getRequestURL();
-        String contextPath = request.getServletContext().getContextPath();
-        return url.delete(url.length() - request.getRequestURI().length(), url.length()).append(contextPath).toString();
-    }
-}

+ 0 - 104
ruoyi-common/src/main/java/com/ruoyi/common/filter/XssHttpServletRequestWrapper.java

@@ -1,104 +0,0 @@
-package com.ruoyi.common.filter;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import javax.servlet.ReadListener;
-import javax.servlet.ServletInputStream;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletRequestWrapper;
-import org.apache.commons.io.IOUtils;
-import org.springframework.http.HttpHeaders;
-import org.springframework.http.MediaType;
-import com.ruoyi.common.utils.StringUtils;
-import com.ruoyi.common.utils.html.EscapeUtil;
-
-/**
- * XSS过滤处理
- * 
- * @author ruoyi
- */
-public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper
-{
-    /**
-     * @param request
-     */
-    public XssHttpServletRequestWrapper(HttpServletRequest request)
-    {
-        super(request);
-    }
-
-    @Override
-    public String[] getParameterValues(String name)
-    {
-        String[] values = super.getParameterValues(name);
-        if (values != null)
-        {
-            int length = values.length;
-            String[] escapseValues = new String[length];
-            for (int i = 0; i < length; i++)
-            {
-                // 防xss攻击和过滤前后空格
-                escapseValues[i] = EscapeUtil.clean(values[i]).trim();
-            }
-            return escapseValues;
-        }
-        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();
-        final ByteArrayInputStream bis = new ByteArrayInputStream(json.getBytes("utf-8"));
-        return new ServletInputStream()
-        {
-            @Override
-            public boolean isFinished()
-            {
-                return true;
-            }
-
-            @Override
-            public boolean isReady()
-            {
-                return true;
-            }
-
-            @Override
-            public void setReadListener(ReadListener readListener)
-            {
-            }
-
-            @Override
-            public int read() throws IOException
-            {
-                return bis.read();
-            }
-        };
-    }
-
-    /**
-     * 是否是Json请求
-     * 
-     * @param request
-     */
-    public boolean isJsonRequest()
-    {
-        String header = super.getHeader(HttpHeaders.CONTENT_TYPE);
-        return MediaType.APPLICATION_JSON_VALUE.equalsIgnoreCase(header);
-    }
-}

+ 0 - 36
ruoyi-common/src/main/resources/i18n/messages.properties

@@ -1,36 +0,0 @@
-#错误消息
-not.null=* 必须填写
-user.jcaptcha.error=验证码错误
-user.jcaptcha.expire=验证码已失效
-user.not.exists=用户不存在/密码错误
-user.password.not.match=用户不存在/密码错误
-user.password.retry.limit.count=密码输入错误{0}次
-user.password.retry.limit.exceed=密码输入错误{0}次,帐户锁定10分钟
-user.password.delete=对不起,您的账号已被删除
-user.blocked=用户已封禁,请联系管理员
-role.blocked=角色已封禁,请联系管理员
-user.logout.success=退出成功
-
-length.not.valid=长度必须在{min}到{max}个字符之间
-
-user.username.not.valid=* 2到20个汉字、字母、数字或下划线组成,且必须以非数字开头
-user.password.not.valid=* 5-50个字符
- 
-user.email.not.valid=邮箱格式错误
-user.mobile.phone.number.not.valid=手机号格式错误
-user.login.success=登录成功
-user.notfound=请重新登录
-user.forcelogout=管理员强制退出,请重新登录
-user.unknown.error=未知错误,请重新登录
-
-##文件上传消息
-upload.exceed.maxSize=上传的文件大小超出限制的文件大小!<br/>允许的文件最大大小是:{0}MB!
-upload.filename.exceed.length=上传的文件名最长{0}个字符
-
-##权限
-no.permission=您没有数据的权限,请联系管理员添加权限 [{0}]
-no.create.permission=您没有创建数据的权限,请联系管理员添加权限 [{0}]
-no.update.permission=您没有修改数据的权限,请联系管理员添加权限 [{0}]
-no.delete.permission=您没有删除数据的权限,请联系管理员添加权限 [{0}]
-no.export.permission=您没有导出数据的权限,请联系管理员添加权限 [{0}]
-no.view.permission=您没有查看数据的权限,请联系管理员添加权限 [{0}]

File diff suppressed because it is too large
+ 1848 - 109
sql/ruoyi-vue-pro.sql


+ 12 - 1
src/main/java/cn/iocoder/dashboard/framework/web/config/WebConfiguration.java

@@ -1,10 +1,12 @@
 package cn.iocoder.dashboard.framework.web.config;
 
 import cn.iocoder.dashboard.framework.web.core.filter.RequestBodyCacheFilter;
+import cn.iocoder.dashboard.framework.web.core.filter.XssFilter;
 import org.springframework.boot.context.properties.EnableConfigurationProperties;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.core.annotation.Order;
+import org.springframework.util.PathMatcher;
 import org.springframework.web.bind.annotation.RestController;
 import org.springframework.web.cors.CorsConfiguration;
 import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
@@ -18,7 +20,7 @@ import javax.annotation.Resource;
  * Web 配置类
  */
 @Configuration
-@EnableConfigurationProperties(WebProperties.class)
+@EnableConfigurationProperties({WebProperties.class, XssProperties.class})
 public class WebConfiguration implements WebMvcConfigurer {
 
     @Resource
@@ -60,4 +62,13 @@ public class WebConfiguration implements WebMvcConfigurer {
         return new RequestBodyCacheFilter();
     }
 
+    /**
+     * 创建 XssFilter Bean,解决 Xss 安全问题
+     */
+    @Bean
+    @Order(Integer.MIN_VALUE + 1000) // 需要保证在 RequestBodyCacheFilter 后面
+    public XssFilter xssFilter(XssProperties properties, PathMatcher pathMatcher) {
+        return new XssFilter(properties, pathMatcher);
+    }
+
 }

+ 29 - 0
src/main/java/cn/iocoder/dashboard/framework/web/config/XssProperties.java

@@ -0,0 +1,29 @@
+package cn.iocoder.dashboard.framework.web.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.validation.annotation.Validated;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Xss 配置属性
+ *
+ * @author 芋道源码
+ */
+@ConfigurationProperties(prefix = "yudao.xss")
+@Validated
+@Data
+public class XssProperties {
+
+    /**
+     * 是否开启,默认为 true
+     */
+    private boolean enable = true;
+    /**
+     * 需要排除的 URL,默认为空
+     */
+    private List<String> excludeUrls = Collections.emptyList();
+
+}

+ 51 - 0
src/main/java/cn/iocoder/dashboard/framework/web/core/filter/XssFilter.java

@@ -0,0 +1,51 @@
+package cn.iocoder.dashboard.framework.web.core.filter;
+
+import cn.iocoder.dashboard.framework.web.config.XssProperties;
+import lombok.AllArgsConstructor;
+import org.springframework.util.PathMatcher;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * Xss 过滤器
+ *
+ * 对 Xss 不了解的胖友,可以看看 http://www.iocoder.cn/Fight/The-new-girl-asked-me-why-AJAX-requests-are-not-secure-I-did-not-answer/
+ *
+ * @author 芋道源码
+ */
+@AllArgsConstructor
+public class XssFilter extends OncePerRequestFilter {
+
+    /**
+     * 属性
+     */
+    private final XssProperties properties;
+    /**
+     * 路径匹配器
+     */
+    private final PathMatcher pathMatcher;
+
+    @Override
+    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
+            throws IOException, ServletException {
+        filterChain.doFilter(new XssRequestWrapper(request), response);
+    }
+
+    @Override
+    protected boolean shouldNotFilter(HttpServletRequest request) {
+        // 如果关闭,则不过滤
+        if (!properties.isEnable()) {
+            return true;
+        }
+
+        // 如果匹配到无需过滤,则不过滤
+        String uri = request.getRequestURI();
+        return properties.getExcludeUrls().stream().anyMatch(excludeUrl -> pathMatcher.match(excludeUrl, uri));
+    }
+
+}

+ 92 - 0
src/main/java/cn/iocoder/dashboard/framework/web/core/filter/XssRequestWrapper.java

@@ -0,0 +1,92 @@
+package cn.iocoder.dashboard.framework.web.core.filter;
+
+import cn.hutool.core.io.IoUtil;
+import cn.hutool.core.util.ReflectUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.http.HTMLFilter;
+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.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+
+/**
+ * Xss 请求 Wrapper
+ *
+ * @author 芋道源码
+ */
+public class XssRequestWrapper extends HttpServletRequestWrapper {
+
+    /**
+     * 基于线程级别的 HTMLFilter 对象,因为它线程非安全
+     */
+    private static final ThreadLocal<HTMLFilter> HTML_FILTER = ThreadLocal.withInitial(() -> {
+        HTMLFilter htmlFilter = new HTMLFilter();
+        // 反射修改 encodeQuotes 属性为 false,避免 " 被转移成 &quot; 字符
+        ReflectUtil.setFieldValue(htmlFilter, "encodeQuotes", false);
+        return htmlFilter;
+    });
+
+    public XssRequestWrapper(HttpServletRequest request) {
+        super(request);
+    }
+
+    private static String filterHtml(String content) {
+        if (StrUtil.isEmpty(content)) {
+            return content;
+        }
+        return HTML_FILTER.get().filter(content);
+    }
+
+    // ========== IO 流相关 ==========
+
+    @Override
+    public BufferedReader getReader() throws IOException {
+        return new BufferedReader(new InputStreamReader(this.getInputStream()));
+    }
+
+    @Override
+    public ServletInputStream getInputStream() throws IOException {
+        // 如果非 json 请求,不进行 Xss 处理
+        if (!StrUtil.startWithIgnoreCase(super.getContentType(), MediaType.APPLICATION_JSON_VALUE)) {
+            return super.getInputStream();
+        }
+
+        // 读取内容,并过滤
+        String content = IoUtil.readUtf8(super.getInputStream());
+        content = filterHtml(content);
+        final ByteArrayInputStream newInputStream = new ByteArrayInputStream(content.getBytes());
+        // 返回 ServletInputStream
+        return new ServletInputStream() {
+
+            @Override
+            public int read() {
+                return newInputStream.read();
+            }
+
+            @Override
+            public boolean isFinished() {
+                return true;
+            }
+
+            @Override
+            public boolean isReady() {
+                return true;
+            }
+
+            @Override
+            public void setReadListener(ReadListener readListener) {}
+
+        };
+    }
+
+    // ========== Param 相关 ==========
+
+    // ========== Header 相关 ==========
+
+}

+ 0 - 1
src/main/java/cn/iocoder/dashboard/framework/web/core/package-info.java

@@ -1 +0,0 @@
-package cn.iocoder.dashboard.framework.web.core;

+ 1 - 1
src/main/java/cn/iocoder/dashboard/modules/system/service/auth/impl/SysAuthServiceImpl.java

@@ -85,7 +85,7 @@ public class SysAuthServiceImpl implements SysAuthService {
     @Override
     public String login(SysAuthLoginReqVO reqVO, String userIp, String userAgent) {
         // 判断验证码是否正确
-        this.verifyCaptcha(reqVO.getUsername(), reqVO.getUuid(), reqVO.getCode());
+//        this.verifyCaptcha(reqVO.getUsername(), reqVO.getUuid(), reqVO.getCode());
 
         // 使用账号密码,进行登陆。
         LoginUser loginUser = this.login0(reqVO.getUsername(), reqVO.getPassword());

+ 31 - 0
src/main/java/cn/iocoder/dashboard/util/object/ReflectUtils.java

@@ -0,0 +1,31 @@
+package cn.iocoder.dashboard.util.object;
+
+import cn.hutool.core.util.ReflectUtil;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+
+/**
+ * 反射 Util 工具类,解决 {@link cn.hutool.core.util.ReflectUtil} 无法满足的情况
+ *
+ * @author 芋道源码
+ */
+public class ReflectUtils {
+
+    public static void setFinalFieldValue(Object obj, String fieldName, Object value) {
+        // 获得 Field
+        if (obj == null) {
+            return;
+        }
+        Field field = ReflectUtil.getField(obj.getClass(), fieldName);
+        if (field == null) {
+            return;
+        }
+
+        // 获得该 Field 的 modifiers 属性,为非 final
+        ReflectUtil.setFieldValue(field, "modifiers", field.getModifiers() & ~Modifier.FINAL);
+        // 真正,设置值
+        ReflectUtil.setFieldValue(obj, field, value);
+    }
+
+}