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