TRX преди 1 година
родител
ревизия
7f66775c9d

+ 24 - 16
src/main/java/com/zswl/dataservice/auth/OpenAPIInterceptor.java

@@ -18,7 +18,9 @@ import org.apache.commons.codec.binary.StringUtils;
 import org.springframework.http.HttpStatus;
 import org.springframework.web.servlet.HandlerInterceptor;
 import org.springframework.web.servlet.ModelAndView;
+import org.springframework.web.util.ContentCachingResponseWrapper;
 
+import java.io.PrintWriter;
 import java.util.Map;
 
 
@@ -43,25 +45,31 @@ public class OpenAPIInterceptor implements HandlerInterceptor {
         boolean isInBlackList = openApiVerifyService.isInBlackList(request);
         if (isInBlackList) {
             // 在黑名单当中
-            response.setCharacterEncoding("UTF-8");
-            response.setHeader("Content-Type", "application/json");
-            response.setStatus(HttpStatus.PAYMENT_REQUIRED.value());
-
-            JSONObject data = new JSONObject();
-            data.put("message", "拒绝请求");
-            data.put("code", 402);
-            data.put("state", "Fail");
-            data.put("success", false);
-            data.put("failed", true);
-
-            response.getWriter().write(data.toJSONString());
-            response.getWriter().flush();
-            response.getWriter().close();
+            ResultContent resultContent = ResultContent.buildFail("拒绝请求", 402);
+            String str = JSONUtil.toJsonStr(resultContent);
+            if (response instanceof ContentCachingResponseWrapper) {
+                ContentCachingResponseWrapper responseWrapper = (ContentCachingResponseWrapper) response;
+                responseWrapper.setCharacterEncoding("UTF-8");
+                responseWrapper.setHeader("Content-Type", "application/json");
+                responseWrapper.setStatus(HttpStatus.PAYMENT_REQUIRED.value());
+                PrintWriter printWriter = responseWrapper.getWriter();
+                printWriter.write(str);
+                printWriter.flush();
+                printWriter.close();
+                responseWrapper.copyBodyToResponse();
+            } else {
+                response.setCharacterEncoding("UTF-8");
+                response.setHeader("Content-Type", "application/json");
+                response.setStatus(HttpStatus.PAYMENT_REQUIRED.value());
+                response.getWriter().write(str);
+                response.getWriter().flush();
+                response.getWriter().close();
+            }
             return false;
         }
 
         if (path != null && path.indexOf(openApi) > 0) {
-            return openApiVerifyService.verify(request, response, object);
+            return openApiVerifyService.verifyOpenAPI(request, response, object);
         }
         return true;
     }
@@ -70,7 +78,7 @@ public class OpenAPIInterceptor implements HandlerInterceptor {
     public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
         String path = request.getRequestURI();
         if (path != null && path.indexOf(openApi) > 0) {
-            openApiVerifyService.saveLog(request, response, handler, modelAndView);
+            openApiVerifyService.saveOpenAPILog(request, response, handler, modelAndView);
         }
     }
 

+ 1 - 0
src/main/java/com/zswl/dataservice/dao/openApi/OpenApiSignInfoDao.java

@@ -21,4 +21,5 @@ public interface OpenApiSignInfoDao extends MongoDao<OpenApiSignInfo>, OpenApiSi
 
     OpenApiSignInfo findTopByName(String name);
 
+    OpenApiSignInfo findTopByAppId(String appId);
 }

+ 13 - 0
src/main/java/com/zswl/dataservice/dataConfig/OpenAPIConfig.java

@@ -1,5 +1,8 @@
 package com.zswl.dataservice.dataConfig;
 
+import javax.xml.transform.sax.SAXResult;
+import java.util.List;
+
 /**
  * @author TRX
  * @date 2024/9/14
@@ -9,4 +12,14 @@ public class OpenAPIConfig {
     // openAPI日志保存时间
     public static final Long logTTL = 90 * 24 * 60 * 60 * 1000L;
 
+    // authorization 头标记
+    public static final String OPENBODYSIG = "OPEN-BODY-SIG";
+
+    // 请求数据最大长度
+    public static final int maxLen = 5000;
+
+    public static final List<String> authKeys = List.of("AppId", "Timestamp", "Nonce", "Signature");
+
+    // authorization 请求时间戳和系统的差距不等大于 5 分钟
+    public static final long timeBetween = 5 * 60 * 1000L;
 }

+ 169 - 27
src/main/java/com/zswl/dataservice/service/openApi/OpenApiVerifyService.java

@@ -5,11 +5,14 @@ import cn.hutool.json.JSONUtil;
 import com.zswl.dataservice.auth.OpenAPIContext;
 import com.zswl.dataservice.dao.openApi.BlackListDao;
 import com.zswl.dataservice.dao.openApi.OpenApiRequestLogDao;
+import com.zswl.dataservice.dao.openApi.OpenApiSignInfoDao;
 import com.zswl.dataservice.dataConfig.OpenAPIConfig;
 import com.zswl.dataservice.domain.openApi.OpenApiRequestLog;
+import com.zswl.dataservice.domain.openApi.OpenApiSignInfo;
 import com.zswl.dataservice.service.base.RedisService;
 import com.zswl.dataservice.service.user.OperationLogsService;
 import com.zswl.dataservice.type.OperationLogType;
+import com.zswl.dataservice.utils.AesUtils;
 import com.zswl.dataservice.utils.DateUtils;
 import com.zswl.dataservice.utils.HttpUtils;
 import com.zswl.dataservice.utils.mqtt.type.LogsLevel;
@@ -20,11 +23,15 @@ import jakarta.servlet.ServletInputStream;
 import jakarta.servlet.ServletOutputStream;
 import jakarta.servlet.http.HttpServletRequest;
 import jakarta.servlet.http.HttpServletResponse;
+import lombok.Data;
 import lombok.SneakyThrows;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.ObjectUtils;
+import org.apache.commons.lang3.StringUtils;
 import org.aspectj.apache.bcel.classfile.Module;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.ApplicationContext;
+import org.springframework.http.HttpStatus;
 import org.springframework.stereotype.Service;
 import org.springframework.web.context.request.RequestContextHolder;
 import org.springframework.web.context.request.ServletRequestAttributes;
@@ -35,7 +42,9 @@ import org.springframework.web.util.ContentCachingResponseWrapper;
 import java.io.BufferedReader;
 import java.io.InputStreamReader;
 import java.io.OutputStream;
+import java.io.PrintWriter;
 import java.util.Date;
+import java.util.HashMap;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.TimeUnit;
@@ -63,6 +72,9 @@ public class OpenApiVerifyService {
     @Autowired
     BlackListDao blackListDao;
 
+    @Autowired
+    OpenApiSignInfoDao openApiSignInfoDao;
+
     //线程池
     ExecutorService executorService = Executors.newFixedThreadPool(SystemUtil.getCpuCoreCount() * 2);
 
@@ -82,16 +94,125 @@ public class OpenApiVerifyService {
      * @return
      */
     @SneakyThrows
-    public boolean verify(HttpServletRequest request, HttpServletResponse response, Object object) {
+    public boolean verifyOpenAPI(HttpServletRequest request, HttpServletResponse response, Object object) {
         log.info("---------------------openAPI验证----------------------");
         OpenAPIContext.setTime(System.currentTimeMillis());
         if (request instanceof ContentCachingRequestWrapper) {
             ContentCachingRequestWrapper contentCachingRequestWrapper = (ContentCachingRequestWrapper) request;
+            ContentCachingResponseWrapper responseWrapper = (ContentCachingResponseWrapper) response;
             String requestStr = contentCachingRequestWrapper.getContentAsString();
-            log.info("body {}", requestStr);
+
+            String msg = "认证错误";
+            int code = 403;
+            boolean isSuccess = false;
+            if (contentCachingRequestWrapper.getContentLength() > OpenAPIConfig.maxLen) {
+                setResponse(request, responseWrapper, "请求内容过大", 403, false);
+                return false;
+            }
+            log.info("请求数据requestStr: {}", requestStr);
+
             String authorization = request.getHeader("authorization");
+            log.info("authorization数据 {}", authorization);
+            if (StringUtils.isNotEmpty(authorization)) {
+                if (authorization.length() > OpenAPIConfig.maxLen) {
+                    setResponse(request, responseWrapper, "请求内容过大", 403, false);
+                    return false;
+                }
+                authorization = authorization.replaceAll(" ", "");
+                if (authorization.startsWith(OpenAPIConfig.OPENBODYSIG)) {
+                    authorization = authorization.replace(OpenAPIConfig.OPENBODYSIG, "");
+                    HashMap<String, String> map = new HashMap<>();
+                    String[] arr = authorization.split(",");
+                    isSuccess = true;
+
+                    if (arr.length > 0) {
+                        for (String s : arr) {
+                            for (String key : OpenAPIConfig.authKeys) {
+                                if (s.startsWith(key)) {
+                                    String val = s.replace(key + "=", "");
+                                    val = val.replace("\"", "");
+                                    map.put(key, val);
+                                    break;
+                                }
+                            }
+                        }
+                    }
 
+                    if (isSuccess) {
+                        OpenApiSignInfo signInfo = null;
+                        if (!map.containsKey("AppId")) {
+                            isSuccess = false;
+                            msg = "没有AppId信息";
+                        }
+                        String appId = map.get("AppId");
+                        signInfo = openApiSignInfoDao.findTopByAppId(appId);
+                        if (ObjectUtils.isEmpty(signInfo)) {
+                            isSuccess = false;
+                            msg = "AppId错误";
+                        }
+                        if (isSuccess && !map.containsKey("Timestamp")) {
+                            isSuccess = false;
+                            msg = "没有Timestamp信息";
+                        }
+                        String timestamp = map.get("Timestamp");
+                        if (StringUtils.isEmpty(timestamp) || timestamp.length() != 14) {
+                            isSuccess = false;
+                            msg = "Timestamp格式错误";
+                        }
 
+                        if (isSuccess){
+                            Long time = DateUtils.timeToLong(timestamp, DateUtils.unionAuth);
+                            if (time == null || Math.abs(time - System.currentTimeMillis()) > OpenAPIConfig.timeBetween) {
+                                isSuccess = false;
+                                msg = "Timestamp不符合要求";
+                            }
+                        }
+
+                        if (isSuccess && !map.containsKey("Nonce")) {
+                            isSuccess = false;
+                            msg = "没有Nonce信息";
+                        }
+                        String nonce = map.get("Nonce");
+                        if (isSuccess && (StringUtils.isEmpty(nonce) || nonce.length() > 50)) {
+                            isSuccess = false;
+                            msg = "Nonce为空或长度不符合要求";
+                        }
+                        if (isSuccess && !map.containsKey("Signature")) {
+                            isSuccess = false;
+                            msg = "没有Signature信息";
+                        }
+                        String signature = map.get("Signature");
+                        if (isSuccess && (StringUtils.isEmpty(signature) || signature.length() > 200)) {
+                            isSuccess = false;
+                            msg = "Signature为空或长度不符合要求";
+                        }
+
+                        if(isSuccess) {
+                            String sign = AesUtils.signData(requestStr);
+                            log.info("数据sign {}", sign);
+                            String appKey = signInfo.getAppKey();
+                            String c = String.format("%s%s%s%s", appId, timestamp, nonce, sign);
+                            String tempSignature = AesUtils.signMacSHA256(c, appKey);
+                            log.info("系统tempSignature {}", tempSignature);
+                            log.info("传入signature {}", signature);
+
+                            if (!tempSignature.equals(signature)) {
+                                isSuccess = false;
+                                msg = "Signature认证错误";
+                            }
+                        }
+                    }
+                } else {
+                    msg = "报文头没有OPEN-BODY-SIG标记";
+                }
+            } else {
+                msg = "报文头authorization为空";
+            }
+            if (!isSuccess) {
+                // 验证不成功
+                setResponse(request, responseWrapper, msg, code, true);
+                return false;
+            }
         }
         return true;
     }
@@ -105,8 +226,48 @@ public class OpenApiVerifyService {
      * @param modelAndView
      */
     @SneakyThrows
-    public void saveLog(HttpServletRequest request, HttpServletResponse response,
-            Object handler, ModelAndView modelAndView) {
+    public void saveOpenAPILog(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
+        ResultContent resultContent = null;
+        if (response instanceof ContentCachingResponseWrapper) {
+            ContentCachingResponseWrapper responseWrapper = (ContentCachingResponseWrapper) response;
+            byte[] bytes = responseWrapper.getContentAsByteArray();
+            resultContent = HttpUtils.toBean(new String(bytes), ResultContent.class);
+            responseWrapper.copyBodyToResponse();
+        }
+        saveLog(request, resultContent);
+    }
+
+    public boolean isInBlackList(HttpServletRequest request) {
+        String ip = IPUtil.getRemoteIp(request);
+        if (blackListDao.existsByIp(ip)) {
+            ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
+            RequestContextHolder.setRequestAttributes(servletRequestAttributes, true);//设置子线程共享
+            executorService.execute(() -> {
+                operationLogsService.addLogs(String.format("黑名单地址访问: %s", ip), LogsLevel.High, OperationLogType.Black, null);
+            });
+            return true;
+        }
+        return false;
+    }
+
+    @SneakyThrows
+    private void setResponse(HttpServletRequest request, ContentCachingResponseWrapper responseWrapper, String msg, int code, boolean isSaveLog) {
+        ResultContent resultContent = ResultContent.buildFail(msg, code);
+        responseWrapper.setCharacterEncoding("UTF-8");
+        responseWrapper.setHeader("Content-Type", "application/json");
+        responseWrapper.setStatus(HttpStatus.PAYMENT_REQUIRED.value());
+        PrintWriter printWriter = responseWrapper.getWriter();
+        printWriter.write(JSONUtil.toJsonStr(resultContent));
+        printWriter.flush();
+        printWriter.close();
+        responseWrapper.copyBodyToResponse();
+        if (isSaveLog) {
+            saveLog(request, resultContent);
+        }
+        resultContent = null;
+    }
+
+    private void saveLog(HttpServletRequest request, ResultContent resultContent) {
         OpenApiRequestLog openApiRequestLog = new OpenApiRequestLog();
         JSONObject param = HttpUtils.getRequestObj(request);
         if (param != null) {
@@ -129,30 +290,11 @@ public class OpenApiVerifyService {
         }
         openApiRequestLog.setAuthorization(request.getHeader("authorization"));
         openApiRequestLog.setTTL(new Date(System.currentTimeMillis() + OpenAPIConfig.logTTL));
-        if (response instanceof ContentCachingResponseWrapper) {
-            ContentCachingResponseWrapper responseWrapper = (ContentCachingResponseWrapper) response;
-            byte[] bytes = responseWrapper.getContentAsByteArray();
-            ResultContent resultContent = HttpUtils.toBean(new String(bytes), ResultContent.class);
-            if (resultContent != null) {
-                openApiRequestLog.setIsSuccess(resultContent.isSuccess());
-                openApiRequestLog.setErrorMsg(resultContent.getMsg());
-            }
-            openApiRequestLog.setResponseBody(resultContent);
-            responseWrapper.copyBodyToResponse();
+        if (resultContent != null) {
+            openApiRequestLog.setIsSuccess(resultContent.isSuccess());
+            openApiRequestLog.setErrorMsg(resultContent.getMsg());
         }
+        openApiRequestLog.setResponseBody(resultContent);
         openApiRequestLogDao.save(openApiRequestLog);
     }
-
-    public boolean isInBlackList(HttpServletRequest request) {
-        String ip = IPUtil.getRemoteIp(request);
-        if (blackListDao.existsByIp(ip)) {
-            ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
-            RequestContextHolder.setRequestAttributes(servletRequestAttributes, true);//设置子线程共享
-            executorService.execute(() -> {
-                operationLogsService.addLogs(String.format("黑名单地址访问: %s", ip), LogsLevel.High, OperationLogType.Black, null);
-            });
-            return true;
-        }
-        return false;
-    }
 }

+ 99 - 0
src/main/java/com/zswl/dataservice/utils/AesUtils.java

@@ -0,0 +1,99 @@
+package com.zswl.dataservice.utils;
+
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+import java.nio.charset.StandardCharsets;
+import java.security.KeyFactory;
+import java.security.MessageDigest;
+import java.security.PublicKey;
+import java.security.Security;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.Base64;
+
+import static org.apache.tomcat.util.buf.HexUtils.toHexString;
+
+@Slf4j
+public class AesUtils {
+
+    @SneakyThrows
+    public static String signData(String json_data) {
+        MessageDigest md = MessageDigest.getInstance("SHA-256");
+        md.reset();
+        md.update(json_data.getBytes("utf-8"));
+        return toHexString(md.digest());
+    }
+
+    @SneakyThrows
+    public static String signMacSHA256(String str, String key) {
+        Mac mac = Mac.getInstance("HmacSHA256");
+        mac.init(new SecretKeySpec(key.getBytes("utf-8"), "HmacSHA256"));
+        mac.update(str.getBytes("utf-8"));
+        byte[] arr = mac.doFinal();
+        return Base64.getEncoder().encodeToString(arr);
+    }
+
+    public static String getMD5Str(String s) {
+        try {
+            MessageDigest md = MessageDigest.getInstance("MD5");
+//            byte[] digest = md.digest(s.getBytes());
+//            StringBuilder sb = new StringBuilder();
+//            for (byte b : digest) {
+//                sb.append(String.format("%02x", b));
+//            }
+//            return sb.toString();
+            byte[] bytes = md.digest(s.getBytes("utf-8"));
+            return toHex(bytes);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static String toHex(byte[] bytes) {
+        final char[] HEX_DIGITS = "0123456789ABCDEF".toCharArray();
+        StringBuilder ret = new StringBuilder(bytes.length * 2);
+        for (int i = 0; i < bytes.length; i++) {
+            ret.append(HEX_DIGITS[(bytes[i] >> 4) & 0x0f]);
+            ret.append(HEX_DIGITS[bytes[i] & 0x0f]);
+        }
+        return ret.toString();
+    }
+
+    public static String base64Encode(String str) {
+        if (StringUtils.isNotEmpty(str)) {
+            try {
+                byte[] encoded = Base64.getEncoder().encode(str.getBytes("utf-8"));
+                return new String(encoded);
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+        return str;
+    }
+
+    public static String base64Decode(String str) {
+        if (StringUtils.isNotEmpty(str)) {
+            try {
+                byte[] decoded = Base64.getDecoder().decode(str.getBytes("utf-8"));
+                return new String(decoded);
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+        return str;
+    }
+
+    public static PublicKey initializeSM3WithSM2PublicKey(String publicKeyStr) throws Exception {
+        // 将字节转换为PublicKey
+        Security.addProvider(new BouncyCastleProvider());
+        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(base64Decode(publicKeyStr).getBytes(StandardCharsets.UTF_8));
+        KeyFactory keyFactory = KeyFactory.getInstance("ECDSA", "BC");
+        PublicKey publicKey = keyFactory.generatePublic(keySpec);
+        return publicKey;
+    }
+
+}

+ 22 - 20
src/main/java/com/zswl/dataservice/utils/DateUtils.java

@@ -19,11 +19,32 @@ public class DateUtils {
 
     public final static String FORMAT_LONG = "yyyy-MM-dd HH:mm:ss";
 
+
+    public final static String pattern = "yyyy-MM-dd";
+
+    /**
+     * yyyyMMdd
+     */
+    public final static String pattern1 = "yyyyMMdd";
+
+    public final static String patternStr = "yyyy年MM月dd日";
+
+    public final static String patternyyyyMM = "yyyy-MM";
+
+    public final static String patternyyyyM = "yyyy-M";
+
+    public final static String patternHHmm = "HH:mm";
+
+    public final static String patternyyyyMis = "yyyy-MM-dd HH:mm";
+
+    public final static String patternyyyySSS = "yyyy-MM-dd HH:mm:ss SSS";
+
+    public final static String unionAuth = "yyyyMMddHHmmss";
+
     public static Long timeToLong(String time) {
         return timeToLong(time, FORMAT_LONG);
     }
 
-
     public static Long timeToLong(String time, String format) {
         if (!org.springframework.util.StringUtils.hasText(time)) {
             return null;
@@ -50,25 +71,6 @@ public class DateUtils {
         return null;
     }
 
-    public final static String pattern = "yyyy-MM-dd";
-
-    /**
-     * yyyyMMdd
-     */
-    public final static String pattern1 = "yyyyMMdd";
-
-    public final static String patternStr = "yyyy年MM月dd日";
-
-    public final static String patternyyyyMM = "yyyy-MM";
-
-    public final static String patternyyyyM = "yyyy-M";
-
-    public final static String patternHHmm = "HH:mm";
-
-    public final static String patternyyyyMis = "yyyy-MM-dd HH:mm";
-
-    public final static String patternyyyySSS = "yyyy-MM-dd HH:mm:ss SSS";
-
     public static String paresTime(Long time, String pattern) {
         if (time == null || time <= 0) {
             return "";

+ 5 - 1
src/main/java/com/zswl/dataservice/utils/result/ResultContent.java

@@ -106,10 +106,14 @@ public class ResultContent<T> {
         }
     }
 
-    public boolean isSuccess() {
+    private Boolean isSuccess = false;
+
+    public Boolean isSuccess() {
         return ResultState.Success.equals(this.state);
     }
 
+    private boolean isFailed = false;
+
     public boolean isFailed() {
         return !ResultState.Success.equals(this.state);
     }