系统接口API签名实战

接口安全API签名实例,可在此基础上自定义修改完善。

本文实例基于Spring Boot + Maven + Redis + Hutool工具类实现。

Hutool依赖:

<dependency>
	<groupId>cn.hutool</groupId>
	<artifactId>hutool-all</artifactId>
	<version>5.8.5</version>
</dependency>

一、服务端

1、控制层

用到了Redis缓存,可自行取舍修改。

package com.demo.web.controller.open;

import com.demo.common.core.domain.AjaxResult;
import com.demo.common.core.redis.RedisCache;
import com.demo.common.exception.ServiceException;
import com.demo.common.utils.SignUtil;
import com.demo.common.utils.StringUtils;
import com.demo.system.domain.TestObj;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.concurrent.TimeUnit;

/**
 * 控制层
 *
 * @author 逆行
 * @date 2024/03/27 11:25
 **/
@RestController
@RequestMapping("/open")
public class OpenController
{

    @Resource
    private RedisCache redisCache;//Redis缓存实例

	//自定义的访问标识,可动态配置
    private final String ACCESS_KEY = "自定义的key";//一般足够复杂

	//自定义的访问秘钥,可动态配置
    private final String ACCESS_SECRET = "自定义的秘钥";//一般足够复杂

    /**
     * 用来测试的接口
     *
     * @param obj
     * @param request
     * @return
     */
    @PostMapping("/test")
    public AjaxResult test(@RequestBody TestObj obj, HttpServletRequest request)
    {
        signVerify(request);
        return AjaxResult.success(obj);
    }

    /**
     * 签名验证
     *
     * @param request
     */
    private void signVerify(HttpServletRequest request)
    {
        String accessKey_ = request.getHeader("accessKey");
        String nonce = request.getHeader("nonce");
        String timestamp = request.getHeader("timestamp");
        String body = request.getHeader("body");
        String sign = request.getHeader("sign");
        if (!ACCESS_KEY.equals(accessKey_))
        {
            throw new ServiceException("未授权");
        }
        if (StringUtils.isNotNull(redisCache.getCacheObject(nonce)))
        {
            throw new ServiceException("重复请求");
        }
        if (System.currentTimeMillis() - Long.parseLong(timestamp) > 60 * 1000)
        {
            throw new ServiceException("过期请求");
        }
        String signOfServer = SignUtil.getMD5Sign(nonce + timestamp + body + ACCESS_SECRET);
        if (!signOfServer.equals(sign))
        {
            throw new ServiceException("签名错误");
        }
        redisCache.setCacheObject(nonce, nonce, 1, TimeUnit.MINUTES);
    }
}

2、签名工具类

package com.demo.web.controller.open;

import cn.hutool.crypto.digest.DigestUtil;

/**
 * 签名工具类
 * 
 * @author 逆行
 * @date 2024/03/27 11:30
 **/
public class SignUtil
{
    public static String getMD5Sign(String data)
    {
        return DigestUtil.md5Hex(data);
    }
}

二、客户端

1、调用示例

package com.demo.web.client;

import cn.hutool.core.util.RandomUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.json.JSONUtil;
import com.demo.common.utils.SignUtil;
import com.demo.system.domain.TestObj;

import java.util.HashMap;
import java.util.Map;


/**
 * 模拟客户端API调用测试
 *
 * @author 逆行
 * @date 2024/03/27 11:40
 **/
public class TestClient
{

    private static final String ACCESS_KEY = "自定义的key";

    private static final String ACCESS_SECRET = "自定义的秘钥";

    public static void main(String[] args)
    {
        String requestBody = JSONUtil.toJsonStr(new TestObj("nixing", "man", 25));
        try (HttpResponse response = HttpRequest.post("http://localhost:10030/open/test")
                .addHeaders(getHeaders(requestBody))
                .body(requestBody)
                .execute())
        {
			System.out.println(response.getStatus());
            System.out.println(response.body());
        }
    }

    public static Map<String, String> getHeaders(String requestBody)
    {
        HashMap<String, String> headers = new HashMap<>();
        headers.put("accessKey", ACCESS_KEY);
        String nonce = RandomUtil.randomNumbers(4);
        headers.put("nonce", nonce);
        String timestamp = System.currentTimeMillis() + "";
        headers.put("timestamp", timestamp);
        headers.put("body", requestBody);
        headers.put("sign", SignUtil.getMD5Sign(nonce + timestamp + requestBody + ACCESS_SECRET));
        return headers;
    }
}