From 7470b59060c62c598f9b8daf77d78e1e0e8102c9 Mon Sep 17 00:00:00 2001 From: haozq <1611483981@qq.com> Date: Thu, 21 Aug 2025 14:24:34 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=BC=8F=E6=B4=9E=E5=8F=8A?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E7=BD=91=E5=85=B3=E6=8B=A6=E6=88=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- auth/pom.xml | 6 +- .../common/core/constant/TokenConstants.java | 2 + .../handler/GlobalExceptionHandler.java | 36 +++- gateway/pom.xml | 5 + .../bonus/gateway/config/AuthWriteUtils.java | 1 + .../gateway/config/ContextPathConfig.java | 42 ++++ .../com/bonus/gateway/filter/AuthFilter.java | 13 ++ .../gateway/xss/CacheBodyGlobalFilter.java | 61 ++++++ .../bonus/gateway/xss/XssCleanRuleUtils.java | 124 +++++++++++ .../gateway/xss/XssRequestGlobalFilter.java | 201 ++++++++++++++++++ .../gateway/xss/XssResponseGlobalFilter.java | 104 +++++++++ modules/bmw/pom.xml | 6 +- .../com/bonus/bmw/config/PlanDataConfig.java | 7 +- .../main/resources/static/js/filePreview.js | 2 +- .../bmw/src/main/resources/static/js/main.js | 6 +- .../src/main/resources/static/js/publicJs.js | 4 +- .../AttendanceManage/FaceContrastDetail.js | 2 +- .../AttendanceManageNew/FaceContrastDetail.js | 2 +- .../CertificateManage/CertificatePhoto.js | 2 +- .../Person/PersonContract/ContractDetails.js | 4 +- .../work/Person/personEntry/personEntryUpd.js | 2 +- .../static/js/work/Person/workPayFrom.js | 6 +- .../static/js/work/Person/workPayImage.js | 2 +- .../BasicMsg/SubContractorForm.js | 8 +- .../work/indexScreen/child/personDetails.js | 2 +- modules/file/pom.xml | 5 +- modules/lineProtector/pom.xml | 5 + 27 files changed, 630 insertions(+), 30 deletions(-) create mode 100644 gateway/src/main/java/com/bonus/gateway/config/ContextPathConfig.java create mode 100644 gateway/src/main/java/com/bonus/gateway/xss/CacheBodyGlobalFilter.java create mode 100644 gateway/src/main/java/com/bonus/gateway/xss/XssCleanRuleUtils.java create mode 100644 gateway/src/main/java/com/bonus/gateway/xss/XssRequestGlobalFilter.java create mode 100644 gateway/src/main/java/com/bonus/gateway/xss/XssResponseGlobalFilter.java diff --git a/auth/pom.xml b/auth/pom.xml index 5e27329..648998f 100644 --- a/auth/pom.xml +++ b/auth/pom.xml @@ -57,7 +57,11 @@ com.bonus common-security - + + + org.springframework.boot + spring-boot-starter-actuator + org.springframework.cloud spring-cloud-loadbalancer diff --git a/common/common-core/src/main/java/com/bonus/common/core/constant/TokenConstants.java b/common/common-core/src/main/java/com/bonus/common/core/constant/TokenConstants.java index ec44678..73fd8ed 100644 --- a/common/common-core/src/main/java/com/bonus/common/core/constant/TokenConstants.java +++ b/common/common-core/src/main/java/com/bonus/common/core/constant/TokenConstants.java @@ -12,6 +12,8 @@ public class TokenConstants */ public static final String AUTHENTICATION = "Authorization"; + public static final String TOKEN_HEAD = "token"; + /** * 令牌前缀 */ diff --git a/common/common-security/src/main/java/com/bonus/common/security/handler/GlobalExceptionHandler.java b/common/common-security/src/main/java/com/bonus/common/security/handler/GlobalExceptionHandler.java index eec3b41..5669b65 100644 --- a/common/common-security/src/main/java/com/bonus/common/security/handler/GlobalExceptionHandler.java +++ b/common/common-security/src/main/java/com/bonus/common/security/handler/GlobalExceptionHandler.java @@ -26,9 +26,13 @@ import com.bonus.common.core.web.domain.AjaxResult; public class GlobalExceptionHandler { private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class); + public final static String BODY_ERROR="Required request body is missing:"; + public final static String DATA_ERROR="Data truncation: Data too long for"; + + public final static String NumberFormatException="java.lang.NumberFormatException"; /** - * 权限码异常 + * 权限码异常r */ @ExceptionHandler(NotPermissionException.class) public AjaxResult handleNotPermissionException(NotPermissionException e, HttpServletRequest request) @@ -58,7 +62,7 @@ public class GlobalExceptionHandler { String requestURI = request.getRequestURI(); log.error("请求地址'{}',不支持'{}'请求", requestURI, e.getMethod()); - return AjaxResult.error(e.getMessage()); + return AjaxResult.error(HttpStatus.FORBIDDEN, "请求方式不支持"); } /** @@ -79,6 +83,15 @@ public class GlobalExceptionHandler public AjaxResult handleRuntimeException(RuntimeException e, HttpServletRequest request) { String requestURI = request.getRequestURI(); + String msg=e.getMessage(); + if (StringUtils.hasText(msg)) { + if (msg.contains(BODY_ERROR)){ + return AjaxResult.error("post请求body参数不能为空"); + } + if (msg.contains(DATA_ERROR)){ + return AjaxResult.error("数据长度过长"); + } + } log.error("请求地址'{}',发生未知异常.", requestURI, e); return AjaxResult.error(e.getMessage()); } @@ -94,17 +107,34 @@ public class GlobalExceptionHandler return AjaxResult.error(e.getMessage()); } + /** + * + * @param e + * @return + */ + @ExceptionHandler(NumberFormatException.class) + public AjaxResult numberFormatException(NumberFormatException e) + { + log.error(e.getMessage(), e); + System.err.println(e.getMessage()); + return AjaxResult.error(HttpStatus.FORBIDDEN, "请求参数不正确"); + } /** * 自定义验证异常 */ @ExceptionHandler(BindException.class) public AjaxResult handleBindException(BindException e) { - log.error(e.getMessage(), e); String message = e.getAllErrors().get(0).getDefaultMessage(); + System.err.println(message); + assert message != null; + if(message.contains(NumberFormatException)){ + return AjaxResult.error(HttpStatus.FORBIDDEN, "请求参数不正确"); + } return AjaxResult.error(message); } + /** * 自定义验证异常 */ diff --git a/gateway/pom.xml b/gateway/pom.xml index 4bb8907..06e0dce 100644 --- a/gateway/pom.xml +++ b/gateway/pom.xml @@ -68,6 +68,11 @@ org.springframework.boot spring-boot-starter-actuator + + org.jsoup + jsoup + 1.9.2 + diff --git a/gateway/src/main/java/com/bonus/gateway/config/AuthWriteUtils.java b/gateway/src/main/java/com/bonus/gateway/config/AuthWriteUtils.java index 66d08d1..cd3a352 100644 --- a/gateway/src/main/java/com/bonus/gateway/config/AuthWriteUtils.java +++ b/gateway/src/main/java/com/bonus/gateway/config/AuthWriteUtils.java @@ -36,6 +36,7 @@ public class AuthWriteUtils { public static List getBlackUrl(){ List whiteUrl=new ArrayList<>(); whiteUrl.add("/bmw/**"); + whiteUrl.add("/file/file/ynRealName/**"); return whiteUrl; } diff --git a/gateway/src/main/java/com/bonus/gateway/config/ContextPathConfig.java b/gateway/src/main/java/com/bonus/gateway/config/ContextPathConfig.java new file mode 100644 index 0000000..da853af --- /dev/null +++ b/gateway/src/main/java/com/bonus/gateway/config/ContextPathConfig.java @@ -0,0 +1,42 @@ +package com.bonus.gateway.config; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.web.ServerProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.http.HttpStatus; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.web.server.ResponseStatusException; +import org.springframework.web.server.WebFilter; + +/** + * @author HeiZi + */ +@Configuration +public class ContextPathConfig { + + @Bean + @ConditionalOnProperty("server.servlet.context-path") + @Order(Ordered.HIGHEST_PRECEDENCE) + public WebFilter contextPathWebFilter(ServerProperties serverProperties){ + String contextPath = serverProperties.getServlet().getContextPath(); + return (serverWebExchange, webFilterChain) ->{ + ServerHttpRequest request = serverWebExchange.getRequest(); + String requestPath = request.getURI().getPath(); + + if(requestPath.contains(contextPath)){ + String newPath = requestPath.replaceFirst(contextPath+"/", ""); + ServerHttpRequest newRequest = request.mutate() + .path(newPath).build(); + return webFilterChain.filter(serverWebExchange.mutate() + .request(newRequest) + .build() + ); + }else { + throw new ResponseStatusException(HttpStatus.NOT_FOUND); + } + }; + } +} \ No newline at end of file diff --git a/gateway/src/main/java/com/bonus/gateway/filter/AuthFilter.java b/gateway/src/main/java/com/bonus/gateway/filter/AuthFilter.java index 6863f14..e4da33c 100644 --- a/gateway/src/main/java/com/bonus/gateway/filter/AuthFilter.java +++ b/gateway/src/main/java/com/bonus/gateway/filter/AuthFilter.java @@ -19,6 +19,7 @@ import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.Ordered; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.stereotype.Component; +import org.springframework.util.MultiValueMap; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; @@ -131,6 +132,18 @@ public class AuthFilter implements GlobalFilter, Ordered { token = token.replaceFirst(TokenConstants.PREFIX, StringUtils.EMPTY); } + if(StringUtils.isEmpty(token)){ + String hed="token"; + String nl="null"; + MultiValueMap tokens= request.getQueryParams(); + token = request.getHeaders().getFirst(TokenConstants.TOKEN_HEAD); + if(tokens.get(hed)!=null && !tokens.get(hed).isEmpty()){ + token =tokens.get("token").get(0); + if(nl.equals(token)){ + token=null; + } + } + } return token; } diff --git a/gateway/src/main/java/com/bonus/gateway/xss/CacheBodyGlobalFilter.java b/gateway/src/main/java/com/bonus/gateway/xss/CacheBodyGlobalFilter.java new file mode 100644 index 0000000..89c48ea --- /dev/null +++ b/gateway/src/main/java/com/bonus/gateway/xss/CacheBodyGlobalFilter.java @@ -0,0 +1,61 @@ +package com.bonus.gateway.xss; + + +import org.springframework.cloud.gateway.filter.GatewayFilterChain; +import org.springframework.cloud.gateway.filter.GlobalFilter; +import org.springframework.core.Ordered; +import org.springframework.core.io.buffer.DataBuffer; +import org.springframework.core.io.buffer.DataBufferUtils; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.http.server.reactive.ServerHttpRequestDecorator; +import org.springframework.stereotype.Component; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +/** + * @Author: + * @Description: 这个过滤器解决body不能重复读的问题,为后续的XssRequestGlobalFilter重写post|put请求的body做准备 + * @Date: + *

+ * 没把body的内容放到attribute中去,因为从attribute取出body内容还是需要强转成 Flux,然后转换成String,和直接读取body没有什么区别 + */ +@Component +public class CacheBodyGlobalFilter implements Ordered, GlobalFilter { + @Override + public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { + HttpMethod method = exchange.getRequest().getMethod(); + String contentType = exchange.getRequest().getHeaders().getFirst(HttpHeaders.CONTENT_TYPE); + if (method == HttpMethod.POST || method == HttpMethod.PUT) { + if (MediaType.APPLICATION_FORM_URLENCODED_VALUE.equalsIgnoreCase(contentType) + || MediaType.APPLICATION_JSON_VALUE.equalsIgnoreCase(contentType) + || MediaType.APPLICATION_JSON_UTF8_VALUE.equals(contentType)) { + return DataBufferUtils.join(exchange.getRequest().getBody()) + .flatMap(dataBuffer -> { + DataBufferUtils.retain(dataBuffer); + Flux cachedFlux = Flux + .defer(() -> Flux.just(dataBuffer.slice(0, dataBuffer.readableByteCount()))); + ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator( + exchange.getRequest()) { + @Override + public Flux getBody() { + return cachedFlux; + } + }; + return chain.filter(exchange.mutate().request(mutatedRequest).build()); + }); + } + + } + return chain.filter(exchange); + } + + @Override + public int getOrder() { + return Ordered.HIGHEST_PRECEDENCE; + } +} + diff --git a/gateway/src/main/java/com/bonus/gateway/xss/XssCleanRuleUtils.java b/gateway/src/main/java/com/bonus/gateway/xss/XssCleanRuleUtils.java new file mode 100644 index 0000000..f785769 --- /dev/null +++ b/gateway/src/main/java/com/bonus/gateway/xss/XssCleanRuleUtils.java @@ -0,0 +1,124 @@ +package com.bonus.gateway.xss; + + + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.safety.Whitelist; +import org.springframework.core.io.ClassPathResource; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Iterator; +import java.util.regex.Pattern; + +/** + * @Author: + * @Description: xss过滤工具 + * @Date: + */ +public class XssCleanRuleUtils { + + //xss过滤规则(对于script、src及加载事件和弹窗事件的代码块) + private final static Pattern[] scriptPatterns = { + Pattern.compile("", Pattern.CASE_INSENSITIVE), + Pattern.compile("src[\r\n]*=[\r\n]*\\\'(.*?)\\\'", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL), + Pattern.compile("", Pattern.CASE_INSENSITIVE), + Pattern.compile("", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL), + Pattern.compile("eval\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL), + Pattern.compile("expression\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL), + Pattern.compile("javascript:", Pattern.CASE_INSENSITIVE), + Pattern.compile("vbscript:", Pattern.CASE_INSENSITIVE), + Pattern.compile("onload(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL) + }; + + //非富文本的 + public static String xssClean(String value) { + if (value != null) { + value = value.replaceAll("\0|\n|\r", ""); + for (Pattern pattern : scriptPatterns) { + value = pattern.matcher(value).replaceAll(""); + } + value = value.replaceAll("<", "<").replaceAll(">", ">"); + } + return value; + + } + + //富文本的 + public static String xssClean2(String value) { + if (value != null) { + value = value.replaceAll("\0|\n|\r", ""); + for (Pattern pattern : scriptPatterns) { + value = pattern.matcher(value).replaceAll(""); + } + } + return value; + } + + + + //自定义的json白名单 + private static final ClassPathResource jsoupWhiteListPathRes = new ClassPathResource("/json/xssWhiteList.json"); + //配置过滤化参数, 不对代码进行格式化 + private static final Document.OutputSettings outputSettings = new Document.OutputSettings().prettyPrint(false); + //富文本的(使用了Jsoup) + public static String xssRichTextClean(String value) { + // 创建一个自定义的白名单,基于Jsoup的默认白名单 + Whitelist customWhitelist = Whitelist.basic(); + InputStream whiteConfig = null; + try { + whiteConfig = jsoupWhiteListPathRes.getInputStream(); + } catch (IOException e) { + e.printStackTrace(); + } + if (whiteConfig == null) { + throw new RuntimeException("读取jsoup xss 白名单文件失败"); + } else { + try { + JSONObject whiteListJson = JSON.parseObject(whiteConfig, JSONObject.class); + + //添加标签 addTags + JSONArray addTagsJsonArr = whiteListJson.getJSONArray("addTags"); + String[] addTagsArr = addTagsJsonArr.toArray(new String[0]); + customWhitelist.addTags(addTagsArr); + + + //添加属性 addAttributes + JSONArray addAttrJsonArr = whiteListJson.getJSONArray("addAttributes"); + Iterator iter = addAttrJsonArr.iterator(); + while (iter.hasNext()) { + JSONObject attrJsonObj = (JSONObject) iter.next(); + String tag = attrJsonObj.getString("tag"); + JSONArray attrJsonArr = attrJsonObj.getJSONArray("attributes"); + String[] attrArr = attrJsonArr.toArray(new String[0]); + customWhitelist.addAttributes(tag, attrArr); + } + + + //添加 addProtocols + JSONArray addProtoJsonArr = whiteListJson.getJSONArray("addProtocols"); + iter = addProtoJsonArr.iterator(); + while (iter.hasNext()) { + JSONObject attrJsonObj = (JSONObject) iter.next(); + String tag = attrJsonObj.getString("tag"); + String attribute = attrJsonObj.getString("attribute"); + JSONArray protoJsonArr = attrJsonObj.getJSONArray("protocols"); + String[] protocolArr = protoJsonArr.toArray(new String[0]); + customWhitelist.addProtocols(tag, attribute, protocolArr); + } + + + } catch (IOException e) { + e.printStackTrace(); + } + } + value =Jsoup.clean(value, "", customWhitelist, outputSettings); + return value; + } +} + + diff --git a/gateway/src/main/java/com/bonus/gateway/xss/XssRequestGlobalFilter.java b/gateway/src/main/java/com/bonus/gateway/xss/XssRequestGlobalFilter.java new file mode 100644 index 0000000..1445932 --- /dev/null +++ b/gateway/src/main/java/com/bonus/gateway/xss/XssRequestGlobalFilter.java @@ -0,0 +1,201 @@ +package com.bonus.gateway.xss; +import com.bonus.common.core.utils.StringUtils; +import com.bonus.gateway.config.properties.XssProperties; +import io.netty.buffer.ByteBufAllocator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cloud.gateway.filter.GatewayFilterChain; +import org.springframework.cloud.gateway.filter.GlobalFilter; +import org.springframework.core.Ordered; +import org.springframework.core.io.buffer.DataBuffer; +import org.springframework.core.io.buffer.DataBufferUtils; +import org.springframework.core.io.buffer.NettyDataBufferFactory; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.http.server.reactive.ServerHttpRequestDecorator; +import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.stereotype.Component; +import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.util.UriComponentsBuilder; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.net.URI; +import java.nio.CharBuffer; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.atomic.AtomicReference; +/** + * @Author: + * @Description: 自定义防XSS攻击网关全局过滤器 + * @Date: + */ + +@Component +public class XssRequestGlobalFilter implements GlobalFilter, Ordered { + @Autowired + private XssProperties xss; + private Logger logger = LoggerFactory.getLogger(XssRequestGlobalFilter.class); + /** + * + * @param exchange + * @param chain + * @return + * + * get请求参考spring cloud gateway自带过滤器: + * @see org.springframework.cloud.gateway.filter.factory.AddRequestParameterGatewayFilterFactory + * + * post请求参考spring cloud gateway自带过滤器: + * @see org.springframework.cloud.gateway.filter.factory.rewrite.ModifyRequestBodyGatewayFilterFactory + */ + @Override + public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain){ + // grab configuration from Config object + logger.info("----自定义防XSS攻击网关全局过滤器生效----"); + String path = exchange.getRequest().getPath().toString(); + ServerHttpRequest serverHttpRequest = exchange.getRequest(); + HttpMethod method = serverHttpRequest.getMethod(); + String contentType = serverHttpRequest.getHeaders().getFirst(HttpHeaders.CONTENT_TYPE); + + Boolean postFlag = (method == HttpMethod.POST || method == HttpMethod.PUT) && + (MediaType.APPLICATION_FORM_URLENCODED_VALUE.equalsIgnoreCase(contentType) || MediaType.APPLICATION_JSON_VALUE.equals(contentType) || MediaType.APPLICATION_JSON_UTF8_VALUE.equals(contentType)); + + // get 请求, 参考的是 org.springframework.cloud.gateway.filter.factory.AddRequestParameterGatewayFilterFactory + if (method == HttpMethod.GET) { + URI uri = exchange.getRequest().getURI(); + + String rawQuery = uri.getRawQuery(); + if (StringUtils.isBlank(rawQuery)){ + return chain.filter(exchange); + } + rawQuery = XssCleanRuleUtils.xssClean(rawQuery); + try { + URI newUri = UriComponentsBuilder.fromUri(uri) + .replaceQuery(rawQuery) + .build(true) + .toUri(); + + ServerHttpRequest request = exchange.getRequest().mutate() + .uri(newUri).build(); + return chain.filter(exchange.mutate().request(request).build()); + } catch (Exception e) { + logger.error("get请求清理xss攻击异常", e); + throw new IllegalStateException("Invalid URI query: \"" + rawQuery + "\""); + } + } + //post请求时,如果是文件上传之类的请求,不修改请求消息体 + else if (postFlag){ + // 参考的是 org.springframework.cloud.gateway.filter.factory.AddRequestParameterGatewayFilterFactory + + //从请求里获取Post请求体 + String bodyStr = resolveBodyFromRequest(serverHttpRequest); + // 这种处理方式,必须保证post请求时,原始post表单必须有数据过来,不然会报错 + if (StringUtils.isBlank(bodyStr)) { + logger.error("请求异常:{} POST请求必须传递参数", serverHttpRequest.getURI().getRawPath()); + ServerHttpResponse response = exchange.getResponse(); + response.setStatusCode(HttpStatus.BAD_REQUEST); + byte[] bytes = "{\"code\":400,\"msg\":\"post data error\"}".getBytes(StandardCharsets.UTF_8); + DataBuffer buffer = response.bufferFactory().wrap(bytes); + return response.writeWith(Mono.just(buffer)); + } + //白名单处理(看业务需求) + String url = exchange.getRequest().getURI().getPath(); + boolean containsTarget =StringUtils.matches(url, xss.getExcludeUrls()); + if (containsTarget) { + //bodyStr = XssCleanRuleUtils.xssRichTextClean(bodyStr); + bodyStr = XssCleanRuleUtils.xssClean2(bodyStr); + } else { + bodyStr = XssCleanRuleUtils.xssClean(bodyStr); + } + + URI uri = serverHttpRequest.getURI(); + URI newUri = UriComponentsBuilder.fromUri(uri).build(true).toUri(); + ServerHttpRequest request = exchange.getRequest().mutate().uri(newUri).build(); + DataBuffer bodyDataBuffer = stringBuffer(bodyStr); + Flux bodyFlux = Flux.just(bodyDataBuffer); + + // 定义新的消息头 + HttpHeaders headers = new HttpHeaders(); + headers.putAll(exchange.getRequest().getHeaders()); + + // 由于修改了传递参数,需要重新设置CONTENT_LENGTH,长度是字节长度,不是字符串长度 + int length = bodyStr.getBytes().length; + headers.remove(HttpHeaders.CONTENT_LENGTH); + headers.setContentLength(length); + + // 设置CONTENT_TYPE + if (StringUtils.isNotBlank(contentType)) { + headers.set(HttpHeaders.CONTENT_TYPE, contentType); + } + + // 由于post的body只能订阅一次,由于上面代码中已经订阅过一次body。所以要再次封装请求到request才行,不然会报错请求已经订阅过 + request = new ServerHttpRequestDecorator(request) { + @Override + public HttpHeaders getHeaders() { + long contentLength = headers.getContentLength(); + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.putAll(super.getHeaders()); + if (contentLength > 0) { + httpHeaders.setContentLength(contentLength); + } else { + // this causes a 'HTTP/1.1 411 Length Required' on httpbin.org + httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked"); + } + return httpHeaders; + } + + @Override + public Flux getBody() { + return bodyFlux; + } + }; + + //封装request,传给下一级 + request.mutate().header(HttpHeaders.CONTENT_LENGTH, Integer.toString(bodyStr.length())); + return chain.filter(exchange.mutate().request(request).build()); + } else { + return chain.filter(exchange); + } + + } + + @Override + public int getOrder() { + return -90; + } + + /** + * 从Flux中获取字符串的方法 + * @return 请求体 + */ + private String resolveBodyFromRequest(ServerHttpRequest serverHttpRequest) { + //获取请求体 + Flux body = serverHttpRequest.getBody(); + AtomicReference bodyRef = new AtomicReference<>(); + body.subscribe(buffer -> { + CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer.asByteBuffer()); + DataBufferUtils.release(buffer); + bodyRef.set(charBuffer.toString()); + }); + //获取request body + return bodyRef.get(); + } + + /** + * 字符串转DataBuffer + * @param value + * @return + */ + private DataBuffer stringBuffer(String value) { + byte[] bytes = value.getBytes(StandardCharsets.UTF_8); + NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT); + DataBuffer buffer = nettyDataBufferFactory.allocateBuffer(bytes.length); + buffer.write(bytes); + return buffer; + } + +} + diff --git a/gateway/src/main/java/com/bonus/gateway/xss/XssResponseGlobalFilter.java b/gateway/src/main/java/com/bonus/gateway/xss/XssResponseGlobalFilter.java new file mode 100644 index 0000000..6929ddb --- /dev/null +++ b/gateway/src/main/java/com/bonus/gateway/xss/XssResponseGlobalFilter.java @@ -0,0 +1,104 @@ +package com.bonus.gateway.xss; +import com.bonus.common.core.utils.StringUtils; +import com.bonus.gateway.config.properties.XssProperties; +import lombok.extern.slf4j.Slf4j; +import org.reactivestreams.Publisher; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cloud.gateway.filter.GatewayFilterChain; +import org.springframework.cloud.gateway.filter.GlobalFilter; +import org.springframework.core.Ordered; +import org.springframework.core.io.buffer.DataBuffer; +import org.springframework.core.io.buffer.DataBufferFactory; +import org.springframework.core.io.buffer.DataBufferUtils; +import org.springframework.core.io.buffer.DefaultDataBufferFactory; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.http.server.reactive.ServerHttpResponseDecorator; +import org.springframework.stereotype.Component; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.nio.charset.Charset; + +/** + * @Author: + * @Description: 重写Response,防止xss攻击 + * @Date: + */ +@Component +@Slf4j +public class XssResponseGlobalFilter implements Ordered, GlobalFilter { + + @Autowired + private XssProperties xss; + + + @Override + public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { + //获取请求url + String path = exchange.getRequest().getPath().toString(); + + ServerHttpResponse originalResponse = exchange.getResponse(); + DataBufferFactory bufferFactory = originalResponse.bufferFactory(); + ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) { + @Override + public Mono writeWith(Publisher body) { + String contentType = getDelegate().getHeaders().getFirst(HttpHeaders.CONTENT_TYPE); + Boolean flag = MediaType.APPLICATION_JSON_VALUE.equals(contentType) || MediaType.APPLICATION_JSON_UTF8_VALUE.equals(contentType); + if (body instanceof Flux && flag) { + Flux fluxBody = (Flux) body; + return super.writeWith(fluxBody.buffer().map(dataBuffer -> { + //如果响应过大,会进行截断,出现乱码, + //然后看api DefaultDataBufferFactory有个join方法可以合并所有的流,乱码的问题解决 + DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory(); + DataBuffer join = null; + try { + join = dataBufferFactory.join(dataBuffer); + byte[] content = new byte[join.readableByteCount()]; + join.read(content); + //释放掉内存 + DataBufferUtils.release(join); + String result = new String(content, Charset.forName("UTF-8")); + //logger.info("result:"+result); + //若为带有富文本的接口,走富文本xss过滤 + String url = exchange.getRequest().getURI().getPath(); + boolean containsTarget = StringUtils.matches(url, xss.getExcludeUrls()); + + if (containsTarget) { + //result = XssCleanRuleUtils.xssRichTextClean(result); + result = XssCleanRuleUtils.xssClean2(result); + } else { + //result就是response的值,对result进行去XSS + result = XssCleanRuleUtils.xssClean(result); + } + byte[] uppedContent = new String(result.getBytes(), Charset.forName("UTF-8")).getBytes(); + return bufferFactory.wrap(uppedContent); + } catch (Exception e) { + // 处理异常,记录日志等 + throw e; + } finally { + if (join != null) { + //释放掉内存 + DataBufferUtils.release(join); + } + } + })); + } + // if body is not a flux. never got there. + return super.writeWith(body); + } + }; + // replace response with decorator + return chain.filter(exchange.mutate().response(decoratedResponse).build()); + } + + @Override + public int getOrder() { + return -50; + } +} + diff --git a/modules/bmw/pom.xml b/modules/bmw/pom.xml index 38fe4b1..2c67e50 100644 --- a/modules/bmw/pom.xml +++ b/modules/bmw/pom.xml @@ -188,7 +188,11 @@ junit junit - + + + org.springframework.boot + spring-boot-starter-actuator + org.java-websocket Java-WebSocket diff --git a/modules/bmw/src/main/java/com/bonus/bmw/config/PlanDataConfig.java b/modules/bmw/src/main/java/com/bonus/bmw/config/PlanDataConfig.java index aca55a8..89b15bf 100644 --- a/modules/bmw/src/main/java/com/bonus/bmw/config/PlanDataConfig.java +++ b/modules/bmw/src/main/java/com/bonus/bmw/config/PlanDataConfig.java @@ -8,11 +8,12 @@ public class PlanDataConfig { /** * url地址 */ - public String winUrl = "http://192.168.0.14:1918/ynPlan"; + public String winUrl = "http://192.168.0.14:9100/ynRealName/ynPlan"; + //、public String winUrl = "http://192.168.0.14:1918/ynPlan"; // public String linUrl = "http://112.29.103.165:1918/ynPlan"; - public String linUrl = "http://192.168.0.14:1918/ynPlan"; - +// public String linUrl = "http://192.168.0.14:1918/ynPlan"; + public String linUrl = "http://192.168.0.14:9100/ynRealName/ynPlan"; /** * 保存token的key */ diff --git a/modules/bmw/src/main/resources/static/js/filePreview.js b/modules/bmw/src/main/resources/static/js/filePreview.js index 3e79555..37ff831 100644 --- a/modules/bmw/src/main/resources/static/js/filePreview.js +++ b/modules/bmw/src/main/resources/static/js/filePreview.js @@ -94,7 +94,7 @@ function filePreview(url) { if (~url.indexOf("http")) { path = url; } else { - path = fileUrl + url; + path = fileUrl + url+"?token="+Authorization; } console.log("url:", path); let fileToken = generateToken(); diff --git a/modules/bmw/src/main/resources/static/js/main.js b/modules/bmw/src/main/resources/static/js/main.js index 707b505..5477501 100644 --- a/modules/bmw/src/main/resources/static/js/main.js +++ b/modules/bmw/src/main/resources/static/js/main.js @@ -97,7 +97,7 @@ function showLoginInfo() { var pro = window.location.protocol; var host = window.location.host; - var domain = pro + "//" + host; + var domain = pro + "//" + host; var sex = data.sex; var url = data.headImgUrl; @@ -108,9 +108,9 @@ function showLoginInfo() { url = ctxPath + "/img/avatars/yn.png"; } - url = domain + url; + } else { - url = domain + "/statics" + url; + url = domain + "/ynRealName/bmw/statics" + url; } var img = $(".admin-header-user img"); img.attr("src", url); diff --git a/modules/bmw/src/main/resources/static/js/publicJs.js b/modules/bmw/src/main/resources/static/js/publicJs.js index 9ccff88..5d54ce1 100644 --- a/modules/bmw/src/main/resources/static/js/publicJs.js +++ b/modules/bmw/src/main/resources/static/js/publicJs.js @@ -1,6 +1,6 @@ -var ctxPath = getContextPath(); -var currentHostname = window.location.hostname; let IP_URL="http://127.0.0.1:9100/ynRealName" +var ctxPath = IP_URL+"/bmw"; +var currentHostname = window.location.hostname; // //测试 // var loginPath = "http://" + currentHostname + ":9200"; diff --git a/modules/bmw/src/main/resources/static/js/work/Person/AttendanceManage/FaceContrastDetail.js b/modules/bmw/src/main/resources/static/js/work/Person/AttendanceManage/FaceContrastDetail.js index 630c092..0205e59 100644 --- a/modules/bmw/src/main/resources/static/js/work/Person/AttendanceManage/FaceContrastDetail.js +++ b/modules/bmw/src/main/resources/static/js/work/Person/AttendanceManage/FaceContrastDetail.js @@ -49,7 +49,7 @@ function findImg(){ if(data.code==200 && list.length>0){ for(var i=0;i'; + html+=''; html+='上传人:'+list[i].name+'
'; html+='上传时间:'+list[i].addTime+'
'; html+=''; diff --git a/modules/bmw/src/main/resources/static/js/work/Person/AttendanceManageNew/FaceContrastDetail.js b/modules/bmw/src/main/resources/static/js/work/Person/AttendanceManageNew/FaceContrastDetail.js index 630c092..359fbf0 100644 --- a/modules/bmw/src/main/resources/static/js/work/Person/AttendanceManageNew/FaceContrastDetail.js +++ b/modules/bmw/src/main/resources/static/js/work/Person/AttendanceManageNew/FaceContrastDetail.js @@ -49,7 +49,7 @@ function findImg(){ if(data.code==200 && list.length>0){ for(var i=0;i'; + html+=''; html+='上传人:'+list[i].name+'
'; html+='上传时间:'+list[i].addTime+'
'; html+=''; diff --git a/modules/bmw/src/main/resources/static/js/work/Person/CertificateManage/CertificatePhoto.js b/modules/bmw/src/main/resources/static/js/work/Person/CertificateManage/CertificatePhoto.js index b6ed8c0..7f5249e 100644 --- a/modules/bmw/src/main/resources/static/js/work/Person/CertificateManage/CertificatePhoto.js +++ b/modules/bmw/src/main/resources/static/js/work/Person/CertificateManage/CertificatePhoto.js @@ -13,7 +13,7 @@ function printPhoto(){ path = filePreviewPath + "/" + url; } // for(var i=0;i'; + html+=''; // } $("#photoDiv").append(html); diff --git a/modules/bmw/src/main/resources/static/js/work/Person/PersonContract/ContractDetails.js b/modules/bmw/src/main/resources/static/js/work/Person/PersonContract/ContractDetails.js index 6ac34b2..173254d 100644 --- a/modules/bmw/src/main/resources/static/js/work/Person/PersonContract/ContractDetails.js +++ b/modules/bmw/src/main/resources/static/js/work/Person/PersonContract/ContractDetails.js @@ -21,11 +21,11 @@ function findContractDetails(idNumber,id){ var obj=data.data; var bean=obj.bean; if(bean !=null){ - var faceUrl=fileUrl + "/"+bean.faceUrl;//人脸照片路径 + var faceUrl=fileUrl + "/"+bean.faceUrl+"?token="+Authorization;//人脸照片路径 var shortMessage=bean.shortMessage;//短信信息 var message=bean.message;//验证码 var messageTime=bean.messageTime;//验证码时间 - var videoUrl=fileUrl + "/"+bean.videoUrl;//视频路径 + var videoUrl=fileUrl + "/"+bean.videoUrl+"?token="+Authorization;//视频路径 $("#faceUrl").attr("src",faceUrl); $("#shortMessage").text(shortMessage); diff --git a/modules/bmw/src/main/resources/static/js/work/Person/personEntry/personEntryUpd.js b/modules/bmw/src/main/resources/static/js/work/Person/personEntry/personEntryUpd.js index fc052c0..cd757ec 100644 --- a/modules/bmw/src/main/resources/static/js/work/Person/personEntry/personEntryUpd.js +++ b/modules/bmw/src/main/resources/static/js/work/Person/personEntry/personEntryUpd.js @@ -1094,7 +1094,7 @@ function setData(data) { htmlCardPath += '
' htmlCardPath += '' htmlCardPath += '
' - htmlCardPath += '' + htmlCardPath += '' htmlCardPath += '
' + (wageCardName[wageCardName.length - 1].length > 8 ? (wageCardName[wageCardName.length - 1].substr(0, 8) + "..") : wageCardName[wageCardName.length - 1]) + '
' htmlCardPath += '' htmlCardPath += '' diff --git a/modules/bmw/src/main/resources/static/js/work/Person/workPayFrom.js b/modules/bmw/src/main/resources/static/js/work/Person/workPayFrom.js index 6819fe4..72e5294 100644 --- a/modules/bmw/src/main/resources/static/js/work/Person/workPayFrom.js +++ b/modules/bmw/src/main/resources/static/js/work/Person/workPayFrom.js @@ -172,7 +172,7 @@ function view() { html += ''; }else{ let x = fileUrl + "/" + data[i]; - html += ''; + html += ''; } $("#view1").append(html); document.getElementById("view1").ondblclick= function () { @@ -184,7 +184,7 @@ function view() { html += ''; }else{ let x = fileUrl + "/" + data[i]; - html += ''; + html += ''; } $("#view2").append(html); document.getElementById("view2").ondblclick= function () { @@ -196,7 +196,7 @@ function view() { html += ''; }else{ let x = fileUrl + "/" + data[i]; - html += ''; + html += ''; } $("#view3").append(html); document.getElementById("view3").ondblclick= function () { diff --git a/modules/bmw/src/main/resources/static/js/work/Person/workPayImage.js b/modules/bmw/src/main/resources/static/js/work/Person/workPayImage.js index fa8305a..70290b2 100644 --- a/modules/bmw/src/main/resources/static/js/work/Person/workPayImage.js +++ b/modules/bmw/src/main/resources/static/js/work/Person/workPayImage.js @@ -27,7 +27,7 @@ function viewImg(idNumber) { html += ''; }else{ let x = fileUrl + "/" + data[i]; - html += ''; + html += ''; } } $("#imgView").append(html); diff --git a/modules/bmw/src/main/resources/static/js/work/SubContractor/BasicMsg/SubContractorForm.js b/modules/bmw/src/main/resources/static/js/work/SubContractor/BasicMsg/SubContractorForm.js index 68b1b37..cbc9ee2 100644 --- a/modules/bmw/src/main/resources/static/js/work/SubContractor/BasicMsg/SubContractorForm.js +++ b/modules/bmw/src/main/resources/static/js/work/SubContractor/BasicMsg/SubContractorForm.js @@ -495,25 +495,25 @@ function setData(data) { $("#preview1").css("display", ""); var demo1 = $("#demo1"); var html = ''; - html += ''; + html += ''; demo1.append(html); $("#preview2").css("display", ""); var demo2 = $("#demo2"); var html = ''; - html += ''; + html += ''; demo2.append(html); $("#preview3").css("display", ""); var demo3 = $("#demo3"); var html = ''; - html += ''; + html += ''; demo3.append(html); $("#preview4").css("display", ""); var demo4 = $("#demo4"); var html = ''; - html += ''; + html += ''; demo4.append(html); } diff --git a/modules/bmw/src/main/resources/static/js/work/indexScreen/child/personDetails.js b/modules/bmw/src/main/resources/static/js/work/indexScreen/child/personDetails.js index 9b9419a..f90ca3a 100644 --- a/modules/bmw/src/main/resources/static/js/work/indexScreen/child/personDetails.js +++ b/modules/bmw/src/main/resources/static/js/work/indexScreen/child/personDetails.js @@ -56,7 +56,7 @@ function setData() { localStorage.setItem('contractTypes', data.contractBean.contractType); } //人脸照片 - $('.basicContentLeft>img').attr('src', fileUrl + '/' + data.facePhoto); + $('.basicContentLeft>img').attr('src', fileUrl + '/' + data.facePhoto+"?token="+Authorization); //红绿灯 if(Number(data.lightStatus) == 0){ $('.basicContentRight>img').attr('src','../../../../img/work/indexScreen/redLamp.png') diff --git a/modules/file/pom.xml b/modules/file/pom.xml index f6ce6da..c349ce8 100644 --- a/modules/file/pom.xml +++ b/modules/file/pom.xml @@ -24,7 +24,10 @@ system ${project.basedir}/lib/aspose-words-15.8.0-jdk16.jar
- + + org.springframework.boot + spring-boot-starter-actuator + aspose aspose-slide diff --git a/modules/lineProtector/pom.xml b/modules/lineProtector/pom.xml index 6436682..52c9188 100644 --- a/modules/lineProtector/pom.xml +++ b/modules/lineProtector/pom.xml @@ -48,6 +48,11 @@ 2.2.1 compile + + + org.springframework.boot + spring-boot-starter-actuator +