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

This commit is contained in:
YunaiV
2021-02-21 13:11:27 +08:00
parent 183bb5855a
commit 8605cc35c9
13 changed files with 2069 additions and 329 deletions

View File

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

View File

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

View File

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

View File

@ -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 相关 ==========
}

View File

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

View File

@ -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());

View File

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