package com.smppw.analysis.infrastructure.components; import cn.hutool.core.util.StrUtil; import cn.hutool.crypto.SignUtil; import cn.hutool.json.JSONUtil; import com.smppw.analysis.infrastructure.config.AnalysisProperty; import com.smppw.common.pojo.ResultVo; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.MediaType; import org.springframework.lang.NonNull; import org.springframework.web.servlet.HandlerInterceptor; import java.nio.charset.Charset; import java.time.Duration; import java.util.Map; /** * @author wangzaijun * @date 2023/8/7 15:58 * @description api验证签名拦截器 */ public class ApiSignInterceptor implements HandlerInterceptor { private static final long MAX_TIMEOUT = 50000L; private final Logger logger = LoggerFactory.getLogger(this.getClass()); private final AnalysisProperty property; public ApiSignInterceptor(AnalysisProperty property) { this.property = property; } @Override public boolean preHandle(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull Object handler) throws Exception { if (logger.isInfoEnabled()) { logger.info(StrUtil.format("{} 接口正在被 {} 请求!", request.getRequestURI(), request.getRemoteAddr())); } if (!this.property.getEnableSign()) { if (logger.isDebugEnabled()) { logger.debug("接口不需要签名验证!"); } return HandlerInterceptor.super.preHandle(request, response, handler); } String sign = request.getHeader("sign"); if (StrUtil.isBlank(sign)) { this.writeFailJson(response, "sign 参数为空"); return false; } String timestamp = request.getHeader("timestamp"); if (StrUtil.isBlank(timestamp)) { this.writeFailJson(response, "timestamp 参数为空"); return false; } String appKey = request.getParameter("appKey"); AnalysisProperty.AppSign appSign = this.property.getAppSigns().stream() .filter(e -> appKey.equals(e.getAppKey())).findFirst().orElse(null); if (appSign == null) { this.writeFailJson(response, "appKey非法"); return false; } if (!appSign.getEnabled()) { logger.warn(StrUtil.format("{} 应用未开启接口签名验证!", appKey)); return HandlerInterceptor.super.preHandle(request, response, handler); } boolean flag = this.checkTimestamp(request, response, timestamp, appSign.getTimeout()); if (!flag) { return false; } String appSecret = appSign.getAppSecret(); Map parameterMap = request.getParameterMap(); parameterMap.entrySet().removeIf(next -> "appKey".equalsIgnoreCase(next.getKey())); String paramsSign = SignUtil.signParamsMd5(parameterMap, appSecret); if (!sign.equalsIgnoreCase(paramsSign)) { this.writeFailJson(response, "签名验证失败"); return false; } return HandlerInterceptor.super.preHandle(request, response, handler); } private boolean checkTimestamp(HttpServletRequest request, HttpServletResponse response, String timestamp, Duration timeout) throws Exception { long millis = System.currentTimeMillis(); long time = Long.parseLong(timestamp); long seconds = Math.min(timeout.getSeconds(), MAX_TIMEOUT); if (millis - time > seconds) { logger.error(StrUtil.format("接口有重放攻击,调用方:{}-{}"), request.getRemoteUser(), request.getRemoteAddr()); this.writeFailJson(response, "请求失败!"); return false; } return true; } private synchronized void writeFailJson(HttpServletResponse response, String msg) throws Exception { ResultVo resultVo = ResultVo.fail(400, msg); response.setContentType(MediaType.APPLICATION_JSON_VALUE); response.setCharacterEncoding(Charset.defaultCharset().displayName()); response.getWriter().write(JSONUtil.toJsonStr(resultVo)); response.getWriter().flush(); } }