作用:防止请求参数篡改,限制请求时效性;

常用方式: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("签名成功");
    }
}
View Code

相关文章: