作用:防止请求参数篡改,限制请求时效性;
常用方式:md5签名
关键:签名Key
常用签名原串排列:字母顺序、key1=value1&key2=value2....key (注意:签名规则是双方协商好,不一定是这个规则,这里只是以常规规则举例)
常用排序实现方式,利用TreeMap,进行排序,或者Arrays.sort先对key排序,然后根据key的顺序取值.
具体实现方案:aop统一接口签名
统一签名aop代码如下:
package com.ldp.user.common.aop; import cn.hutool.core.util.StrUtil; import cn.hutool.crypto.SecureUtil; import cn.hutool.extra.servlet.ServletUtil; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.ldp.user.common.exception.ParamException; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; import java.util.Map; import java.util.TreeMap; /** * @Copyright (C) XXXXX技有限公司 * @Author: lidongping * @Date: 2020-12-16 18:36 * @Description: <p> * 接口签名检查aop * </p> */ @Slf4j @Aspect @Component @SuppressWarnings("all") public class SignAspect { private static final String MD5_KEY = "123456"; /** * 切入控制层 */ @Pointcut("execution(* com.ldp.user.controller.*Controller.*(..))") public void controllerAspect() { } /** * 切入控制层基类 */ @Pointcut("execution(* com.ldp.user.common.base.BaseController.*(..))") public void baseControllerAspect() { } @Before("controllerAspect() || baseControllerAspect()") private void before(JoinPoint joinPoint) { HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); // json格式请求的参数 String params = ServletUtil.getBody(request); if (StrUtil.isEmpty(params)) { // key-value请求参数 Map<String, String> paramMap = ServletUtil.getParamMap(request); params = JSONObject.toJSONString(paramMap); } // 签名检查 checkSign(params); } /** * 签名验证的作用: 防止参数篡改 * <p> * 签名规则: * 1. 除sign字段外,所有(有值的字段)字段按照字母顺序的"key1=value1&key2=value2&.....keyn=valuen"格式排序,等到str * 2. str拼接上MD5_KEY,得到strMd5签名原串 * 3. 对strMd5字符串签名,得到strSign签名串 * <p> * 案例 * 请求原数据: { "buyAccount": "wx001", "orderNo": "LDP003", "price": 6990, "productName": "苹果手机", "sign":"06282c01753c2d0b6d45491798b29de5"} * 签名key: 123456 * 签名原串: buyAccount=wx001&orderNo=LDP003&price=6990&productName=苹果手机123456 * 签名值: 06282c01753c2d0b6d45491798b29de5 * 用户签名值: 06282c01753c2d0b6d45491798b29de5 * <p> * 签名值可以在线生成校验 * 在线md5签名链接: https://md5jiami.51240.com/ * * @param params * @throws ParamException */ public void checkSign(String params) throws ParamException { JSONObject jsonObject = JSON.parseObject(params); // 有参数才检查签名 if (jsonObject == null || jsonObject.size() == 0) { return; } // 准备sign String sign = jsonObject.getString("sign"); if (StrUtil.isEmpty(sign)) { throw new ParamException("签名值为空"); } // 删除json中的sign jsonObject.remove("sign"); // 准备签名字符串 StringBuilder sb = new StringBuilder(); // TreeMap能够根据key按照字典排序 Map<String, Object> map = new TreeMap(jsonObject); // 遍历排序的字典,并拼接"key1=value1&key2=value2&.....keyn=valuen"格式 for (Map.Entry<String, Object> entry : map.entrySet()) { Object value = entry.getValue(); String key = entry.getKey(); if (value == null || StrUtil.isEmpty(value.toString())) { continue; } if ("".equals(sb.toString())) { sb.append(key + "=").append(value.toString()); } else { sb.append("&" + key + "=").append(value.toString()); } } String strMd5 = sb.toString() + MD5_KEY; String md5 = SecureUtil.md5(strMd5); log.info("请求原数据:" + params); log.info("签名key:" + MD5_KEY); log.info("签名原串:" + strMd5); log.info("签名值:" + md5); log.info("用户签名值:" + sign); String lowerCase = sign.toLowerCase(); if (!lowerCase.equals(md5)) { throw new ParamException("签名失败"); } log.info("签名成功"); } }