From 56b0a9dca49bf25542c305035fd440879ec21d46 Mon Sep 17 00:00:00 2001 From: luozhun <2025254074@qq.com> Date: Wed, 20 Nov 2024 16:18:03 +0800 Subject: [PATCH] =?UTF-8?q?feat(open-api):=20=E9=87=8D=E6=9E=84=E5=BC=80?= =?UTF-8?q?=E6=94=BE=E6=8E=A5=E5=8F=A3=E7=AD=BE=E5=90=8D=E9=AA=8C=E8=AF=81?= =?UTF-8?q?=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 SignInterceptor 用于签名验证 - 添加 BodyWrapperFilter 用于获取请求体 - 重构 SecurityUnitUseStatisticsDTO 数据结构 - 更新 OpenController 接口 - 修改 FastJson2Config 支持表单数据 --- .../java/com/changhu/config/WebConfig.java | 16 ++- .../changhu/controller/OpenController.java | 1 + .../dto/SecurityUnitUseStatisticsDTO.java | 88 +++++++----- .../support/fastjson2/FastJson2Config.java | 3 +- .../support/filter/BodyWrapperFilter.java | 27 ++++ .../CustomHttpServletRequestWrapper.java | 65 +++++++++ .../support/interceptor/SignInterceptor.java | 126 ++++++++++++++++++ .../main/resources/mapper/OpenApiMapper.xml | 65 ++++++--- 8 files changed, 331 insertions(+), 60 deletions(-) create mode 100644 policeSecurityServer/src/main/java/com/changhu/support/filter/BodyWrapperFilter.java create mode 100644 policeSecurityServer/src/main/java/com/changhu/support/filter/CustomHttpServletRequestWrapper.java create mode 100644 policeSecurityServer/src/main/java/com/changhu/support/interceptor/SignInterceptor.java diff --git a/policeSecurityServer/src/main/java/com/changhu/config/WebConfig.java b/policeSecurityServer/src/main/java/com/changhu/config/WebConfig.java index 48c4125..11f4a19 100644 --- a/policeSecurityServer/src/main/java/com/changhu/config/WebConfig.java +++ b/policeSecurityServer/src/main/java/com/changhu/config/WebConfig.java @@ -2,10 +2,13 @@ package com.changhu.config; import cn.dev33.satoken.interceptor.SaInterceptor; import cn.dev33.satoken.stp.StpUtil; +import com.changhu.support.filter.BodyWrapperFilter; import com.changhu.support.interceptor.JsonBodyInterceptor; -import com.changhu.support.interceptor.OpenApiInterceptor; +import com.changhu.support.interceptor.SignInterceptor; import com.changhu.support.interceptor.UserTypeInterceptor; import org.jetbrains.annotations.NotNull; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; @@ -59,10 +62,19 @@ public class WebConfig implements WebMvcConfigurer { // 注册clientType 拦截器 用于校验当前用户是否匹配操作客户端 registry.addInterceptor(new UserTypeInterceptor()); // 注册开放接口 拦截器 用于校验第三方是否携带指定apiKey - registry.addInterceptor(new OpenApiInterceptor()) + registry.addInterceptor(new SignInterceptor()) .addPathPatterns("/open/**"); } + @Bean + public FilterRegistrationBean loggingFilter() { + FilterRegistrationBean registrationBean = new FilterRegistrationBean<>(); + registrationBean.setFilter(new BodyWrapperFilter()); + registrationBean.addUrlPatterns("/open/*"); // 指定过滤的URL模式 + registrationBean.setOrder(1000); + return registrationBean; + } + @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") diff --git a/policeSecurityServer/src/main/java/com/changhu/controller/OpenController.java b/policeSecurityServer/src/main/java/com/changhu/controller/OpenController.java index a2b0c28..5e17a88 100644 --- a/policeSecurityServer/src/main/java/com/changhu/controller/OpenController.java +++ b/policeSecurityServer/src/main/java/com/changhu/controller/OpenController.java @@ -68,4 +68,5 @@ public class OpenController { public List serviceProjectUserRoster(@Schema(description = "服务项目id") Long serviceProjectId) { return openApiService.serviceProjectUserRoster(serviceProjectId); } + } diff --git a/policeSecurityServer/src/main/java/com/changhu/pojo/dto/SecurityUnitUseStatisticsDTO.java b/policeSecurityServer/src/main/java/com/changhu/pojo/dto/SecurityUnitUseStatisticsDTO.java index 1e20599..406f5c8 100644 --- a/policeSecurityServer/src/main/java/com/changhu/pojo/dto/SecurityUnitUseStatisticsDTO.java +++ b/policeSecurityServer/src/main/java/com/changhu/pojo/dto/SecurityUnitUseStatisticsDTO.java @@ -7,6 +7,8 @@ import com.changhu.module.management.pojo.model.LegalPersonInfo; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; +import java.util.List; + /** * @author 20252 * @createTime 2024/11/18 上午10:32 @@ -14,41 +16,10 @@ import lombok.Data; */ @Data public class SecurityUnitUseStatisticsDTO { - @Schema(description = "服务项目id") - private Long serviceProjectId; - @Schema(description = "公安单位id") - private Long policeUnitId; - @Schema(description = "公安单位名称") - private String policeUnitName; - @Schema(description = "保安单位名称") - private String securityUnitName; - @Schema(description = "服务项目名称") - private String serviceProjectName; - @Schema(description = "保安服务类别") - private ServiceProjectType type; - @Schema(description = "二级类型") - private ServiceProjectTwoType twoType; - @Schema(description = "外包公司名称") - private String outsourceName; - - @Schema(description = "保安人员总数") - private Integer securityUserTotal; - @Schema(description = "持证保安数量") - private Integer haveCardSecurityUserCount; - - @Schema(description = "是否备案") - private IsOrNot isFiling; - @Schema(description = "证件号(保安服务许可证/备案证)") - private String idNumber; - - @Schema(description = "保安单位法人信息") - private LegalPersonInfo securityUnitLegalPersonInfo; - - @Schema(description = "项目经理") - private String serviceProjectManager; - @Schema(description = "项目经理联系方式") - private String serviceProjectManagerTelephone; - + @Schema(description = "事业单位id") + private Long enterprisesUnitId; + @Schema(description = "事业单位名称") + private String enterprisesUnitName; @Schema(description = "省") private String provinceName; @Schema(description = "市") @@ -57,5 +28,52 @@ public class SecurityUnitUseStatisticsDTO { private String districtsName; @Schema(description = "街道") private String streetName; + @Schema(description = "事业单位详细地址") + private String address; + @Schema(description = "服务项目列表") + List<_ServiceProjectVo> serviceProjectList; + + @Schema(description = "公安单位id") + private Long policeUnitId; + @Schema(description = "公安单位名称") + private String policeUnitName; + + @Schema(description = "保安单位id") + private Long securityUnitId; + @Schema(description = "保安单位名称") + private String securityUnitName; + @Schema(description = "保安单位法人信息") + private LegalPersonInfo securityUnitLegalPersonInfo; + + @Schema(description = "项目经理") + private String serviceProjectManager; + @Schema(description = "项目经理联系方式") + private String serviceProjectManagerTelephone; + + + @Data + static class _ServiceProjectVo { + @Schema(description = "服务项目id") + private Long snowFlakeId; + @Schema(description = "服务项目名称") + private String name; + @Schema(description = "保安服务类别") + private ServiceProjectType type; + @Schema(description = "二级类型") + private ServiceProjectTwoType twoType; + + @Schema(description = "外包公司名称") + private String outsourceName; + @Schema(description = "是否备案") + private IsOrNot isFiling; + @Schema(description = "证件号(保安服务许可证/备案证)") + private String idNumber; + + @Schema(description = "保安人员总数") + private Integer securityUserTotal; + @Schema(description = "持证保安数量") + private Integer haveCardSecurityUserCount; + + } } diff --git a/policeSecurityServer/src/main/java/com/changhu/support/fastjson2/FastJson2Config.java b/policeSecurityServer/src/main/java/com/changhu/support/fastjson2/FastJson2Config.java index 10cf050..25ba404 100644 --- a/policeSecurityServer/src/main/java/com/changhu/support/fastjson2/FastJson2Config.java +++ b/policeSecurityServer/src/main/java/com/changhu/support/fastjson2/FastJson2Config.java @@ -86,7 +86,8 @@ public class FastJson2Config { //4.解决中文乱码问题,相当于在Controller上的@RequestMapping中加了个属性produces = "application/json" fastConverter.setSupportedMediaTypes(List.of( - MediaType.APPLICATION_JSON + MediaType.APPLICATION_JSON, + MediaType.APPLICATION_FORM_URLENCODED )); return fastConverter; } diff --git a/policeSecurityServer/src/main/java/com/changhu/support/filter/BodyWrapperFilter.java b/policeSecurityServer/src/main/java/com/changhu/support/filter/BodyWrapperFilter.java new file mode 100644 index 0000000..8cf8a4c --- /dev/null +++ b/policeSecurityServer/src/main/java/com/changhu/support/filter/BodyWrapperFilter.java @@ -0,0 +1,27 @@ +package com.changhu.support.filter; + +import jakarta.servlet.*; +import jakarta.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; + +import java.io.IOException; +import java.util.Objects; + +/** + * @author 20252 + * @createTime 2024/11/19 下午3:07 + * @desc BodyWrapperFilter... + */ +@Slf4j +public class BodyWrapperFilter implements Filter { + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { + log.info("进入filter:{}", servletRequest.getRemoteAddr()); + ServletRequest requestWrapper = null; + if (servletRequest instanceof HttpServletRequest) { + requestWrapper = new CustomHttpServletRequestWrapper((HttpServletRequest) servletRequest); + } + filterChain.doFilter(Objects.requireNonNullElse(requestWrapper, servletRequest), servletResponse); + + } +} diff --git a/policeSecurityServer/src/main/java/com/changhu/support/filter/CustomHttpServletRequestWrapper.java b/policeSecurityServer/src/main/java/com/changhu/support/filter/CustomHttpServletRequestWrapper.java new file mode 100644 index 0000000..a4c11ba --- /dev/null +++ b/policeSecurityServer/src/main/java/com/changhu/support/filter/CustomHttpServletRequestWrapper.java @@ -0,0 +1,65 @@ +package com.changhu.support.filter; + +import jakarta.servlet.ReadListener; +import jakarta.servlet.ServletInputStream; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequestWrapper; + +import java.io.*; +import java.nio.charset.StandardCharsets; + +/** + * @author 20252 + * @createTime 2024/11/19 下午3:12 + * @desc CustomHttpServletRequestWrapper... + */ +public class CustomHttpServletRequestWrapper extends HttpServletRequestWrapper { + private final byte[] body; + + public CustomHttpServletRequestWrapper(HttpServletRequest request) throws IOException { + super(request); + BufferedReader reader = request.getReader(); + try (StringWriter writer = new StringWriter()) { + int read; + char[] buf = new char[1024 * 8]; + while ((read = reader.read(buf)) != -1) { + writer.write(buf, 0, read); + } + this.body = writer.getBuffer().toString().getBytes(); + } + } + + public String getBody() { + return new String(body, StandardCharsets.UTF_8); + } + + @Override + public ServletInputStream getInputStream() { + final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body); + return new ServletInputStream() { + @Override + public boolean isFinished() { + return false; + } + + @Override + public boolean isReady() { + return false; + } + + @Override + public void setReadListener(ReadListener readListener) { + } + + @Override + public int read() { + return byteArrayInputStream.read(); + } + }; + } + + @Override + public BufferedReader getReader() { + return new BufferedReader(new InputStreamReader(this.getInputStream())); + } +} diff --git a/policeSecurityServer/src/main/java/com/changhu/support/interceptor/SignInterceptor.java b/policeSecurityServer/src/main/java/com/changhu/support/interceptor/SignInterceptor.java new file mode 100644 index 0000000..5b668bd --- /dev/null +++ b/policeSecurityServer/src/main/java/com/changhu/support/interceptor/SignInterceptor.java @@ -0,0 +1,126 @@ +package com.changhu.support.interceptor; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.core.util.URLUtil; +import cn.hutool.crypto.digest.MD5; +import cn.hutool.http.HttpUtil; +import com.alibaba.fastjson2.TypeReference; +import com.baomidou.mybatisplus.extension.toolkit.Db; +import com.changhu.common.db.enums.IsEnable; +import com.changhu.common.exception.MessageException; +import com.changhu.common.utils.IpUtil; +import com.changhu.pojo.entity.AccessKeys; +import com.changhu.support.filter.CustomHttpServletRequestWrapper; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.NotNull; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.HandlerInterceptor; + +import java.nio.charset.StandardCharsets; +import java.time.LocalDateTime; +import java.util.*; +import java.util.stream.Collectors; + +/** + * @author 20252 + * @createTime 2024/11/19 下午1:58 + * @desc SignInterceptor... + */ +@Slf4j +public class SignInterceptor implements HandlerInterceptor { + + private static final String ACCESS_KEY = "Access-Key";//调用者身份唯一标识 + private static final String TIMESTAMP = "Time-Stamp";//时间戳 + private static final String SIGN = "Sign";//签名 + + @Override + public boolean preHandle(@NotNull HttpServletRequest request, + @NotNull HttpServletResponse response, + @NotNull Object handler) throws Exception { + if (handler instanceof HandlerMethod) { + String ip = IpUtil.getIp(request); + try { + return checkSign(request); + } catch (MessageException e) { + log.error("开放接口访问失败:{} 访问时间:{} IP:{} 访问接口:{} ", e.getMessage(), LocalDateTime.now(), ip, request.getRequestURI()); + throw e; + } + } + return false; + } + + private boolean checkSign(HttpServletRequest request) throws MessageException { + + String accessKey = request.getHeader(ACCESS_KEY); + String timestamp = request.getHeader(TIMESTAMP); + String sign = request.getHeader(SIGN); + + if (StrUtil.isBlank(accessKey) || StrUtil.isBlank(timestamp) || StrUtil.isBlank(sign)) { + throw new MessageException("请求体缺失"); + } + + AccessKeys accessKeyEntity = Db.lambdaQuery(AccessKeys.class) + .eq(AccessKeys::getAccessKey, accessKey) + .oneOpt() + .orElseThrow(() -> new MessageException("无效的accessKey")); + + if (IsEnable.FALSE.equals(accessKeyEntity.getIsEnable())) { + throw new MessageException("无效的accessKey"); + } + + if (accessKeyEntity.getEffectiveTime() > 0 && System.currentTimeMillis() - Long.parseLong(timestamp) > accessKeyEntity.getEffectiveTime()) { + throw new MessageException("请求已过期"); + } + + List allowedResources = Optional.ofNullable(accessKeyEntity.getAllowedResources()).orElseThrow(() -> new MessageException("暂无允许访问的资源")); + if (!allowedResources.contains(request.getRequestURI())) { + throw new MessageException("无效的请求资源"); + } + + Map hashMap = new HashMap<>(); + //添加请求url参数 + Map map = HttpUtil.decodeParamMap(request.getQueryString(), StandardCharsets.UTF_8); + if (!map.isEmpty()) { + hashMap.putAll(map); + } + + hashMap.put(ACCESS_KEY, accessKey); + hashMap.put(TIMESTAMP, timestamp); + //添加body参数 + CustomHttpServletRequestWrapper c = (CustomHttpServletRequestWrapper) request; + Optional.ofNullable(new TypeReference>() { + }.parseObject(c.getBody())).ifPresent(hashMap::putAll); + + String nowSign = generatedSign(hashMap, accessKeyEntity.getSecretKey()); + if (!sign.equals(nowSign)) { + throw new MessageException("签名错误"); + } + return true; + } + + /** + * 获取签名 + * + * @param map 参数结果 + * @param secretKey 密钥 + * @return 签名字符串 + */ + private String generatedSign(Map map, String secretKey) { + List> entries = new ArrayList<>(map.entrySet()); + String str = entries.stream() + .filter(en -> null == en.getValue() || StrUtil.isNotBlank(en.getValue().toString())) + .sorted(Comparator.comparing(o -> o.getKey().toLowerCase())) + .map(en -> StrUtil.format("{}={}", en.getKey(), URLUtil.encodeAll(en.getValue() + ""))) + .collect(Collectors.joining("&")); + str += ("&Secret-Key=" + secretKey); + return MD5.create().digestHex(str).toUpperCase(); + } + + public static void main(String[] args) { + String str1 = "Access-Key=w2wzi0wefmmo6s735z2el8tfzitya5gj&addr=%E6%B9%96%E5%8D%97%E7%9C%81%E9%95%BF%E6%B2%99%E5%B8%82&age=14&name=zhangsan&Time-Stamp=1732084303463&Secret-Key=db1b5214-02ee-497f-957c-88323b4351bf"; + String str2 = "Access-Key=w2wzi0wefmmo6s735z2el8tfzitya5gj&addr=%E6%B9%96%E5%8D%97%E7%9C%81%E9%95%BF%E6%B2%99%E5%B8%82&age=14&name=zhangsan&Time-Stamp=1732084303463&Secret-Key=db1b5214-02ee-497f-957c-88323b4351bf"; + } + +} diff --git a/policeSecurityServer/src/main/resources/mapper/OpenApiMapper.xml b/policeSecurityServer/src/main/resources/mapper/OpenApiMapper.xml index 0509e9f..5b788ad 100644 --- a/policeSecurityServer/src/main/resources/mapper/OpenApiMapper.xml +++ b/policeSecurityServer/src/main/resources/mapper/OpenApiMapper.xml @@ -33,40 +33,61 @@ column="securityUnitLegalPersonInfo" typeHandler="com.baomidou.mybatisplus.extension.handlers.Fastjson2TypeHandler" property="securityUnitLegalPersonInfo"/> +