|
@@ -0,0 +1,192 @@
|
|
|
|
+package vip.xiaonuo.biz.core.sign.interceptor;
|
|
|
|
+
|
|
|
|
+import cn.hutool.core.convert.Convert;
|
|
|
|
+import cn.hutool.core.util.ObjectUtil;
|
|
|
|
+import com.alibaba.fastjson.JSON;
|
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
|
|
|
+import com.fasterxml.jackson.databind.ObjectMapper;
|
|
|
|
+import jakarta.annotation.Resource;
|
|
|
|
+import jakarta.servlet.http.HttpServletRequest;
|
|
|
|
+import jakarta.servlet.http.HttpServletResponse;
|
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
|
+import org.apache.commons.lang3.StringUtils;
|
|
|
|
+import org.springframework.web.servlet.HandlerInterceptor;
|
|
|
|
+import org.springframework.web.servlet.ModelAndView;
|
|
|
|
+import vip.xiaonuo.biz.core.sign.utils.GenerateSignatureUtil;
|
|
|
|
+import vip.xiaonuo.biz.core.sign.utils.ServletUtils;
|
|
|
|
+import vip.xiaonuo.biz.modular.openinterface.entity.BizOpenInterface;
|
|
|
|
+import vip.xiaonuo.biz.modular.openinterface.service.BizOpenInterfaceService;
|
|
|
|
+import vip.xiaonuo.common.cache.CommonCacheOperator;
|
|
|
|
+import vip.xiaonuo.common.pojo.CommonResult;
|
|
|
|
+import vip.xiaonuo.dev.api.DevConfigApi;
|
|
|
|
+
|
|
|
|
+import java.util.Map;
|
|
|
|
+import java.util.concurrent.ConcurrentHashMap;
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * @Author 徐超
|
|
|
|
+ * @Date 2021/4/20 15:53
|
|
|
|
+ * @Version 1.0
|
|
|
|
+ */
|
|
|
|
+@Slf4j
|
|
|
|
+public class SignAuthInterceptor implements HandlerInterceptor {
|
|
|
|
+
|
|
|
|
+ private static final String NONCE_KEY_STR = "nonce-";
|
|
|
|
+
|
|
|
|
+ private static final String OpenInterfaceKey = "open-interface-";
|
|
|
|
+
|
|
|
|
+ private static final int apiExpireTime = 120;
|
|
|
|
+
|
|
|
|
+ @Resource
|
|
|
|
+ private BizOpenInterfaceService bizOpenInterfaceService;
|
|
|
|
+
|
|
|
|
+ @Resource
|
|
|
|
+ private CommonCacheOperator commonCacheOperator;
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
|
|
|
+
|
|
|
|
+ /***
|
|
|
|
+ * 1、获取请求参数appId
|
|
|
|
+ */
|
|
|
|
+ String appKey = request.getHeader("appKey");
|
|
|
|
+ if (StringUtils.isBlank(appKey)) {
|
|
|
|
+ log.info("appKey不能为空");
|
|
|
|
+ ServletUtils.renderString(response, JSON.toJSONString(CommonResult.error("appKey不能为空")));
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /***
|
|
|
|
+ * 2、获取请求参数secret
|
|
|
|
+ */
|
|
|
|
+ String secret = request.getHeader("appSecret");
|
|
|
|
+ if (StringUtils.isBlank(secret)){
|
|
|
|
+ log.info("appSecret不能为空...........");
|
|
|
|
+ ServletUtils.renderString(response, JSON.toJSONString(CommonResult.error("appSecret不能为空")));
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /***
|
|
|
|
+ * 3、 获取请求参数timestamp 时间戳
|
|
|
|
+ */
|
|
|
|
+ String timestamp = request.getHeader("timestamp");
|
|
|
|
+ if (StringUtils.isBlank(timestamp)){
|
|
|
|
+ log.info("timestamp不能为空...........");
|
|
|
|
+ ServletUtils.renderString(response, JSON.toJSONString(CommonResult.error("timestamp不能为空")));
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /** 4、 防止过期时间的提交
|
|
|
|
+ * 从前端传递的timestamp 与服务器端当前系统时间之差大于120s,则此次请求的timestamp无效
|
|
|
|
+ * 留出短时间考虑网络问题提交速度慢,若时间过长中间时间足以挟持篡改参数,所以折中考虑了120秒
|
|
|
|
+ */
|
|
|
|
+ Long time = System.currentTimeMillis()/1000;
|
|
|
|
+ if (Math.abs(Long.valueOf(timestamp)-time) > apiExpireTime) {
|
|
|
|
+ log.info("timestamp失效...........");
|
|
|
|
+ ServletUtils.renderString(response, JSON.toJSONString(CommonResult.error("timestamp失效")));
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 5、获取请求参数nonce随机数,防止重复的暴力请求
|
|
|
|
+ * 流程:1、获取当前提交的随机数,作为key前往redis 查询,若有值则为重复提交
|
|
|
|
+ * 2、redis中查询不到结果,将当前随机数作为key,value为随机数,过期时间设置为120s
|
|
|
|
+ */
|
|
|
|
+ String nonce = request.getHeader("nonce");
|
|
|
|
+ log.info("nonce:"+nonce);
|
|
|
|
+ if (StringUtils.isBlank(nonce)) {
|
|
|
|
+ log.info("nonce不能为空...........");
|
|
|
|
+ ServletUtils.renderString(response, JSON.toJSONString(CommonResult.error("随机数不能为空")));
|
|
|
|
+ return false;
|
|
|
|
+ } else {
|
|
|
|
+ // 从缓存中取
|
|
|
|
+ Object cacheValue = commonCacheOperator.get(NONCE_KEY_STR+appKey+"-"+nonce);
|
|
|
|
+ if(ObjectUtil.isNotNull(cacheValue)) {
|
|
|
|
+ String redisNonce = Convert.toStr(cacheValue);
|
|
|
|
+ log.info("redisNonce:"+redisNonce);
|
|
|
|
+ log.info("缓存中nonce已存在,请勿重复提交...........");
|
|
|
|
+ ServletUtils.renderString(response, JSON.toJSONString(CommonResult.error("随机数已存在,请勿重复提交")));
|
|
|
|
+ return false;
|
|
|
|
+ }else{
|
|
|
|
+ log.info("redisNonce:"+nonce);
|
|
|
|
+ log.info("缓存nonce..........."+NONCE_KEY_STR+appKey+"-"+nonce);
|
|
|
|
+ commonCacheOperator.put(NONCE_KEY_STR+appKey+"-"+nonce, nonce, 120);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /** 6、验证secret权限、来源是否合法
|
|
|
|
+ * 此处可以用appId,条件为 已开启,未封禁等进行数据库合作机构表查询,有可能已经终止合作禁止了此有用访问,
|
|
|
|
+ * 业务上达到一定条件的可以根据appId分配权限,选择不同的接口能力进行开放
|
|
|
|
+ */
|
|
|
|
+ BizOpenInterface openInterface;
|
|
|
|
+ String openInterfaceStr = null;
|
|
|
|
+ Object openInterfaceValue = commonCacheOperator.get(OpenInterfaceKey + appKey);
|
|
|
|
+ if(ObjectUtil.isNotNull(openInterfaceValue)) {
|
|
|
|
+ openInterfaceStr = Convert.toStr(openInterfaceValue);
|
|
|
|
+ }
|
|
|
|
+ if (ObjectUtil.isNotEmpty(openInterfaceStr)){
|
|
|
|
+ ObjectMapper mapper = new ObjectMapper();
|
|
|
|
+ openInterface = mapper.readValue(openInterfaceStr,BizOpenInterface.class);
|
|
|
|
+ }else{
|
|
|
|
+ openInterface = bizOpenInterfaceService.getOne(new QueryWrapper<BizOpenInterface>().eq("app_key",appKey));
|
|
|
|
+ commonCacheOperator.put(OpenInterfaceKey + appKey,JSON.toJSONString(openInterface));
|
|
|
|
+ }
|
|
|
|
+ if (openInterface == null || !openInterface.getStatus().equals("ENABLE")) {
|
|
|
|
+ log.info("appKey无法查询到合作项目信息或已被封禁...........");
|
|
|
|
+ ServletUtils.renderString(response, JSON.toJSONString(CommonResult.error("无法查询到appKey为【"+appKey+"】的应用信息或应用已关闭,请联系管理员")));
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /***
|
|
|
|
+ * 获取secret 与数据库值对比,判断请求来源是否合法
|
|
|
|
+ */
|
|
|
|
+ if (!secret.equals(openInterface.getAppSecret())) {
|
|
|
|
+ log.info("appSecret与接口提供方不一致...........");
|
|
|
|
+ ServletUtils.renderString(response, JSON.toJSONString(CommonResult.error("appSecret与接口提供方不一致")));
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /***
|
|
|
|
+ * 7、获取请求sign签名参数
|
|
|
|
+ */
|
|
|
|
+ String sign = request.getHeader("sign");
|
|
|
|
+ if (StringUtils.isBlank(sign)){
|
|
|
|
+ log.info("sign不能为空...........");
|
|
|
|
+ ServletUtils.renderString(response, JSON.toJSONString(CommonResult.error("sign不能为空")));
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /***
|
|
|
|
+ * 8、通过后台MD5重新签名校验与前端签名sign值比对,确认当前请求数据是否被篡改
|
|
|
|
+ */
|
|
|
|
+ // 从数组中取出参数放入Map中
|
|
|
|
+ Map<String,String> param = new ConcurrentHashMap<>(5);
|
|
|
|
+ param.put("appKey",request.getHeader("appKey"));
|
|
|
|
+ param.put("appSecret",request.getHeader("appSecret"));
|
|
|
|
+ param.put("timestamp",request.getHeader("timestamp"));
|
|
|
|
+ param.put("nonce",request.getHeader("nonce"));
|
|
|
|
+ param.put("sign",request.getHeader("sign"));
|
|
|
|
+ boolean reuslt = GenerateSignatureUtil.isSignatureValid(param, secret);
|
|
|
|
+ if (!reuslt){
|
|
|
|
+ log.debug("sign签名校验失败...........");
|
|
|
|
+ ServletUtils.renderString(response, JSON.toJSONString(CommonResult.error("sign签名校验失败")));
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+ log.info("签名校验通过,放行...........");
|
|
|
|
+ /***
|
|
|
|
+ * 获取sign签名,与服务端生成的sign 签名对比
|
|
|
|
+ */
|
|
|
|
+ log.info("sign ====================");
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
|
|
|
|
+ log.info("SignAuthInterceptor postHandle====== ");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
|
|
|
|
+ log.info("SignAuthInterceptor afterCompletion====== ");
|
|
|
|
+ }
|
|
|
|
+}
|