- 去 redis 中查找是否有 key 为 nonce:{nonce} 的 string
- 如果没有,则创建这个 key,把这个 key 失效的时间和验证 timestamp 失效的时间一致,比如是 60s 。
- 如果有,说明这个 key 在 60s 内已经被使用了,那么这个请求就可以判断为重放请求 。
文章插图
这种方案nonce和timestamp参数都作为签名的一部分传到后端,基于timestamp方案可以让黑客只能在60s内进行重放攻击,加上nonce随机数以后可以保证接口只能被调用一次,可以很好的解决重放攻击问题 。
代码实现
接下来以SpringBoot项目为例看看如何实现接口的防篡改和防重放功能 。
1、构建请求头对象
@Data @Builder public class requestHeader { private String sign ; private Long timestamp ; private String nonce; }
2、工具类从HttpServletRequest获取请求参数
@Slf4j @UtilityClass public class HttpDataUtil { /** * post请求处理:获取 Body 参数,转换为SortedMap * * @param request */ public SortedMapString> getBodyparams(final HttpServletRequest request) throws IOException { byte[] requestBody = StreamUtils.copyToByteArray(request.getInputStream()); String body = new String(requestBody); return JsonUtil.json2Object(body, SortedMap.class); } ? /** * get请求处理:将URL请求参数转换成SortedMap */ public static SortedMap getUrlParams(HttpServletRequest request) { String param = ""; SortedMap result = new TreeMap<>(); ? if (StringUtils.isEmpty(request.getQueryString())) { return result; } ? try { param = URLdecoder.decode(request.getQueryString(), "utf-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } ? String[] params = param.split("&"); for (String s : params) { String[] array=s.split("="); result.put(array[0], array[1]); } return result; } }
这里的参数放入SortedMap中对其进行字典排序,前端构建签名时同样需要对参数进行字典排序 。
3、签名验证工具类
@Slf4j @UtilityClass public class SignUtil { /** * 验证签名 * 验证算法:把timestamp + JsonUtil.object2Json(SortedMap)合成字符串,然后MD5 */ @SneakyThrows public Boolean verifySign(SortedMap map, RequestHeader requestHeader) { String params = requestHeader.getNonce() + requestHeader.getTimestamp() + JsonUtil.object2Json(map); return verifySign(params, requestHeader); } ? /** * 验证签名 */ public boolean verifySign(String params, RequestHeader requestHeader) { log.debug("客户端签名: {}", requestHeader.getSign()); if (StringUtils.isEmpty(params)) { return false; } log.info("客户端上传内容: {}", params); String paramsSign = DigestUtils.md5DigestAsHex(params.getBytes()).toUpperCase(); log.info("客户端上传内容加密后的签名结果: {}", paramsSign); return requestHeader.getSign().equals(paramsSign); } }
4、HttpServletRequest包装类
public class SignRequestWrApper extends HttpServletRequestWrapper { //用于将流保存下来 private byte[] requestBody = null; ? public SignRequestWrapper(HttpServletRequest request) throws IOException { super(request); requestBody = StreamUtils.copyToByteArray(request.getInputStream()); } ? @Override public ServletInputStream getInputStream() throws IOException { final ByteArrayInputStream bais = new ByteArrayInputStream(requestBody); ? return new ServletInputStream() { @Override public boolean isFinished() { return false; } ? @Override public boolean isReady() { return false; } ? @Override public void setReadListener(ReadListener readListener) { ? } ? @Override public int read() throws IOException { return bais.read(); } }; ? } ? @Override public BufferedReader getReader() throws IOException { return new BufferedReader(new InputStreamReader(getInputStream())); } }
防篡改和防重放我们会通过SpringBoot Filter来实现,而编写的filter过滤器需要读取request数据流,但是request数据流只能读取一次,需要自己实现HttpServletRequestWrapper对数据流包装,目的是将request流保存下来 。
5、创建过滤器实现安全校验
@Configuration public class SignFilterConfiguration { @Value("${sign.maxTime}") private String signMaxTime; ? //filter中的初始化参数 private Map initParametersMap = new HashMap<>(); ? @Bean public FilterRegistrationBean contextFilterRegistrationBean() { initParametersMap.put("signMaxTime",signMaxTime); FilterRegistrationBean registration = new FilterRegistrationBean(); registration.setFilter(signFilter()); registration.setInitParameters(initParametersMap); registration.addUrlPatterns("/sign/*"); registration.setName("SignFilter"); // 设置过滤器被调用的顺序 registration.setOrder(1); return registration; } ? @Bean public Filter signFilter() { return new SignFilter(); } }
推荐阅读
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 图片如何去水印?去水印图片的办法
- 手机如何照片降噪?修复照片用这个方法
- 如何恢复回收站被清空的文件?你必须要知道的4种方法
- u盘损坏后如何恢复数据,教你一招数据立马恢复
- 如何创业起步,创业起步的六个须知事项
- 换手机如何导入通讯录,换手机保存通讯录方法
- 手机视频如何剪辑拼接,手机视频剪辑软件排名
- 生鲜店起名字大全?生鲜店如何起名?
- 日本|“别人向你借钱,如何拒绝不得罪人”?大学生高情商回答被录用
- 推广策划方案范文分享,教你如何做出一份好的推广策划方案