系统接口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;
}
}