From 7672968d78a959559f6067aa9aa13b28dc28f8aa Mon Sep 17 00:00:00 2001 From: zhibing.pu <393733352@qq.com> Date: 星期三, 07 八月 2024 12:06:29 +0800 Subject: [PATCH] 添加网关参数签名校验 --- ruoyi-gateway/src/main/java/com/ruoyi/gateway/filter/XssFilter.java | 115 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 114 insertions(+), 1 deletions(-) diff --git a/ruoyi-gateway/src/main/java/com/ruoyi/gateway/filter/XssFilter.java b/ruoyi-gateway/src/main/java/com/ruoyi/gateway/filter/XssFilter.java index 6fe6285..fe449a9 100644 --- a/ruoyi-gateway/src/main/java/com/ruoyi/gateway/filter/XssFilter.java +++ b/ruoyi-gateway/src/main/java/com/ruoyi/gateway/filter/XssFilter.java @@ -1,7 +1,19 @@ package com.ruoyi.gateway.filter; import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.concurrent.atomic.AtomicReference; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.ruoyi.common.core.constant.HttpStatus; +import com.ruoyi.common.core.constant.TokenConstants; +import com.ruoyi.common.core.utils.ServletUtils; +import org.apache.commons.codec.binary.Base64; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; @@ -34,9 +46,13 @@ @ConditionalOnProperty(value = "security.xss.enabled", havingValue = "true") public class XssFilter implements GlobalFilter, Ordered { + private static final Logger log = LoggerFactory.getLogger(XssFilter.class); // 跨站脚本的 xss 配置,nacos自行添加 @Autowired private XssProperties xss; + + @Value("${security.sign}") + private boolean parameter_signature; @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) @@ -65,6 +81,10 @@ return chain.filter(exchange); } ServerHttpRequestDecorator httpRequestDecorator = requestDecorator(exchange); + if(parameter_signature && !authSign(httpRequestDecorator)){ + log.error("[鉴权签名异常处理]请求路径:{}", exchange.getRequest().getPath()); + return ServletUtils.webFluxResponseWriter(exchange.getResponse(), "签名校验失败", HttpStatus.BAD_REQUEST); + } return chain.filter(exchange.mutate().request(httpRequestDecorator).build()); } @@ -120,7 +140,100 @@ String header = exchange.getRequest().getHeaders().getFirst(HttpHeaders.CONTENT_TYPE); return StringUtils.startsWithIgnoreCase(header, MediaType.APPLICATION_JSON_VALUE); } - + + + /** + * 签名校验 + * @param httpRequestDecorator + * @return + */ + private boolean authSign(ServerHttpRequestDecorator httpRequestDecorator) { + HttpHeaders headers = httpRequestDecorator.getHeaders(); + AtomicReference<JSONObject> jsonObject = new AtomicReference<>(new JSONObject()); + httpRequestDecorator.getBody().buffer().map(dataBuffers -> { + DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory(); + DataBuffer join = dataBufferFactory.join(dataBuffers); + byte[] content = new byte[join.readableByteCount()]; + join.read(content); + DataBufferUtils.release(join); + String bodyStr = new String(content, StandardCharsets.UTF_8); + jsonObject.set(JSON.parseObject(bodyStr)); + + // 防xss攻击过滤 + bodyStr = EscapeUtil.clean(bodyStr); + // 转成字节 + byte[] bytes = bodyStr.getBytes(); + NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT); + DataBuffer buffer = nettyDataBufferFactory.allocateBuffer(bytes.length); + buffer.write(bytes); + return buffer; + }); + JSONObject params = jsonObject.get(); + String sign = headers.getFirst(TokenConstants.SING); + if(StringUtils.isEmpty(sign)){ + return false; + } + String nonce_str = headers.getFirst(TokenConstants.NONCE_STR); + if(StringUtils.isEmpty(nonce_str)){ + return false; + } + + String signUrlEncode = localSignUrl(params, nonce_str); + signUrlEncode = signUrlEncode.replaceAll("& #40;", "\\(") + .replaceAll("& #41;", "\\)") + .replaceAll("\\+", " "); + if(sign.equals(signUrlEncode)){ + return true; + } + return false; + } + + + /** + * 组装签名路径 + * @param params + * @return + */ + public static String localSignUrl(JSONObject params, String key) { + List<String> keySet = new ArrayList<>(params.keySet()); + // 对所有传入参数按照字段名的 ASCII 码从小到大排序(字典序) + Collections.sort(keySet, new Comparator<String>() { + @Override + public int compare(String o1, String o2) { + return o1.compareTo(o2); + } + }); + // 构造签名键值对的格式 + StringBuilder sb = new StringBuilder(); + for (String k : keySet) { + String v = params.getString(k); + if(StringUtils.isNotEmpty(v)){ + sb.append(k + "=" + v + "&"); + } + } + String signUrl = sb.substring(0, sb.length() - 1); + return signUrlEncode(signUrl, key); + } + + + /** + * 签名字符串加密 + * @param signUrl + * @param encryptKey + * @return + */ + public static String signUrlEncode(String signUrl, String encryptKey) { + byte[] signByte = new byte[0]; + try { + signByte = HMACSHA1.HmacSHA1Encrypt(signUrl, encryptKey); + } catch (Exception e) { + throw new RuntimeException(e); + } + String localSign = Base64.encodeBase64String(signByte); + return localSign; + } + + @Override public int getOrder() { -- Gitblit v1.7.1