一,为什么要给接口做签名验证?

1,app客户端在与服务端通信时,通常都是以接口的形式实现,
这种形式的安全方面有可能出现以下问题:
被非法访问(例如:发短信的接口通常会被利用来垃圾短信)
被重复访问  (例如:在提交订单时多点了几次提交按钮)
而客户端存在的弱点是:对接口站的地址不能轻易修改,
所以我们需要针对从app到接口的接口做签名验证,
接口不能随便app之外的应用访问
 
2,要注意的地方:
   我们给app分配一个app_id和一个app_secret
   app对app_secret的保存要做到不会被轻易的反编译出来,
   否则安全就没有了保障
   android平台建议保存到二进制的so文件中 
 

说明:刘宏缔的架构森林是一个专注架构的博客,地址:https://www.cnblogs.com/architectforest

对应的源码可以访问这里获取: https://github.com/liuhongdi/

说明:作者:刘宏缔 邮箱: 371125307@qq.com

 

二,演示项目的相关信息

  1,项目的地址
https://github.com/liuhongdi/apisign
  2,项目的原理:
给客户端分发:appId,appSecret,version三个字串
appId:分配给客户端的id
appSecret:密钥字串,客户端要安全保存
version:服务端的接口版本
 
客户端在发送请求前,
用appId + appSecret + timestamp +  nonce + version做md5,生成sign字串,
这个字串和appId/timestamp/nonce一起发送到服务端
服务端验证sign是否正确,
如果有误则拦截请求
 
  3,项目的结构 
 如图:
 

三, java代码说明:

1,SignInterceptor.java
@Component
public class SignInterceptor implements HandlerInterceptor {
private static final String SIGN_KEY = "apisign_";
private static final Logger logger = LogManager.getLogger("bussniesslog");
@Resource
private RedisStringUtil redisStringUtil; /*
*@author:liuhongdi
*@date:2020/7/1 下午4:00
*@description:
* @param request:请求对象
* @param response:响应对象
* @param handler:处理对象:controller中的信息 *
* *@return:true表示正常,false表示被拦截
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//依次检查各变量是否存在?
String appId = request.getHeader("appId");
if (StringUtils.isBlank(appId)) {
ServletUtil.renderString(response, JSON.toJSONString(ResultUtil.error(ResponseCode.SIGN_NO_APPID)));
return false;
}
String timestampStr = request.getHeader("timestamp");
if (StringUtils.isBlank(timestampStr)) {
ServletUtil.renderString(response, JSON.toJSONString(ResultUtil.error(ResponseCode.SIGN_NO_TIMESTAMP)));
return false;
}
String sign = request.getHeader("sign");
if (StringUtils.isBlank(sign)) {
ServletUtil.renderString(response, JSON.toJSONString(ResultUtil.error(ResponseCode.SIGN_NO_SIGN)));
return false;
}
String nonce = request.getHeader("nonce");
if (StringUtils.isBlank(nonce)) {
ServletUtil.renderString(response, JSON.toJSONString(ResultUtil.error(ResponseCode.SIGN_NO_NONCE)));
return false;
}
//得到正确的sign供检验用
String origin = appId + Constants.APP_SECRET + timestampStr + nonce + Constants.APP_API_VERSION;
String signEcrypt = MD5Util.md5(origin);
long timestamp = 0;
try {
timestamp = Long.parseLong(timestampStr);
} catch (Exception e) {
logger.error("发生异常",e);
}
//前端的时间戳与服务器当前时间戳相差如果大于180,判定当前请求的timestamp无效
if (Math.abs(timestamp - System.currentTimeMillis() / 1000) > 180) {
ServletUtil.renderString(response, JSON.toJSONString(ResultUtil.error(ResponseCode.SIGN_TIMESTAMP_INVALID)));
return false;
}
//nonce是否存在于redis中,检查当前请求是否是重复请求
boolean nonceExists = redisStringUtil.hasStringkey(SIGN_KEY+timestampStr+nonce);
if (nonceExists) {
ServletUtil.renderString(response, JSON.toJSONString(ResultUtil.error(ResponseCode.SIGN_DUPLICATION)));
return false;
}
//后端MD5签名校验与前端签名sign值比对
if (!(sign.equalsIgnoreCase(signEcrypt))) {
ServletUtil.renderString(response, JSON.toJSONString(ResultUtil.error(ResponseCode.SIGN_VERIFY_FAIL)));
return false;
}
//将timestampstr+nonce存进redis
redisStringUtil.setStringValue(SIGN_KEY+timestampStr+nonce, nonce, 180L);
//sign校验无问题,放行
return true;
} @Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
} @Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}

说明:如果客户端请求的数据缺少会被拦截

与服务端的appSecret等参数md5生成的sign不一致也会被拦截

时间超时/重复请求也会被拦截

2,DefaultMvcConfig.java

@Configuration
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
public class DefaultMvcConfig implements WebMvcConfigurer { @Resource
private SignInterceptor signInterceptor; /**
* 添加Interceptor
* liuhongdi
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(signInterceptor)
.addPathPatterns("/**") //所有请求都需要进行报文签名sign
.excludePathPatterns("/html/*","/js/*"); //排除html/js目录
}
}

说明:用来添加interceptor

 

四,效果验证:

1,js代码实现:
  说明:我们在这里使用js代码供仅演示使用,app_secret作为密钥不能使用js保存:

<body>
<a href="javascript:login('right')">login(right)</a><br/>
<a href="javascript:login('error')">login(error)</a><br/>
<script>
//vars
var appId="wap";
var version="1.0"; //得到sign
function getsign(appSecret,timestamp,nonce) {
var origin = appId + appSecret + timestamp + nonce + version;
console.log("origin:"+origin);
var sign = hex_md5(origin);
return sign;
} //访问login这个api
//说明:这里仅仅是举例子,在ios/android开发中,appSecret要以二进制的形式编译保存
function login(isright) {
//right secret
var appSecret_right="30c722c6acc64306a88dd93a814c9f0a";
//error secret
var appSecret_error="aabbccdd";
var timestamp = parseInt((new Date()).getTime()/1000);
var nonce = Math.floor(Math.random()*8999)+1000;
var sign = "";
if (isright == 'right') {
sign = getsign(appSecret_right,timestamp,nonce);
} else {
sign = getsign(appSecret_error,timestamp,nonce);
}

var postdata = {
username:"a",
password:"b"
} $.ajax({
type:"POST",
url:"/user/login",
data:postdata,
//返回数据的格式
datatype: "json",
//在请求之前调用的函数
beforeSend: function(request) {
request.setRequestHeader("appId", appId);
request.setRequestHeader("timestamp", timestamp);
request.setRequestHeader("sign", sign);
request.setRequestHeader("nonce", nonce);
},
//成功返回之后调用的函数
success:function(data){
if (data.status == 0) {
alert('success:'+data.msg);
} else {
alert("failed:"+data.msg);
}
},
//调用执行后调用的函数
complete: function(XMLHttpRequest, textStatus){
//complete
},
//调用出错执行的函数
error: function(){
//请求出错处理
}
});
}
</script>
</body>

如图:

说明:

login(right):使用正确的appSecret访问login这个接口
login(error):使用错误的appSecret访问login这个接口

 
 
2,查看效果:
成功时返回:
{"status":0,"msg":"操作成功","data":null}
报错时返回:
{"msg":"sign签名校验失败","status":10007}

五,查看spring boot的版本:

  .   ____          _            __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.3.1.RELEASE)

spring boot:给接口增加签名验证(spring boot 2.3.1)的更多相关文章

  1. spring boot: 设计接口站api的版本号,支持次版本号(spring boot 2.3.2)

    一,为什么接口站的api要使用版本号? 1,当服务端接口的功能发生改进后, 客户端如果不更新版本,    则服务端返回的功能可能不能使用,    所以在服务端功能升级后,     客户端也要相应的使用 ...

  2. spring boot rest 接口集成 spring security(2) - JWT配置

    Spring Boot 集成教程 Spring Boot 介绍 Spring Boot 开发环境搭建(Eclipse) Spring Boot Hello World (restful接口)例子 sp ...

  3. spring boot rest 接口集成 spring security(1) - 最简配置

    Spring Boot 集成教程 Spring Boot 介绍 Spring Boot 开发环境搭建(Eclipse) Spring Boot Hello World (restful接口)例子 sp ...

  4. Spring Boot初识(3)- Spring Boot整合Swagger

    一.本文介绍 如果Web项目是完全前后端分离的话(我认为现在完全前后端分离已经是趋势了)一般前端和后端交互都是通过接口的,对接口入参和出参描述的文档就是Mock文档.随着接口数量的增多和参数的个数增加 ...

  5. 47. Spring Boot发送邮件【从零开始学Spring Boot】

    (提供源代码) Spring提供了非常好用的JavaMailSender接口实现邮件发送.在Spring Boot的Starter模块中也为此提供了自动化配置.下面通过实例看看如何在Spring Bo ...

  6. Spring Boot (十): Spring Boot Admin 监控 Spring Boot 应用

    Spring Boot (十): Spring Boot Admin 监控 Spring Boot 应用 1. 引言 在上一篇文章<Spring Boot (九): 微服务应用监控 Spring ...

  7. Spring Boot 2.X(七):Spring Cache 使用

    Spring Cache 简介 在 Spring 3.1 中引入了多 Cache 的支持,在 spring-context 包中定义了org.springframework.cache.Cache 和 ...

  8. spring boot系列(七)spring boot 使用mongodb

    1 pom.xml配置 增加包依赖:spring-boot-starter-data-mongodb <dependency> <groupId>org.springframe ...

  9. Spring Boot 2.0(二):Spring Boot 2.0尝鲜-动态 Banner

    Spring Boot 2.0 提供了很多新特性,其中就有一个小彩蛋:动态 Banner,今天我们就先拿这个来尝尝鲜. 配置依赖 使用 Spring Boot 2.0 首先需要将项目依赖包替换为刚刚发 ...

随机推荐

  1. 说说ERP软件的系统设计--开源软件诞生8

    赤龙ERP系统设计篇--第8篇 用日志记录"开源软件"的诞生 赤龙 ERP 开源地址: 点亮星标,感谢支持,与开发者交流 kzca2000 码云:https://gitee.com ...

  2. 调整JavaScript抽象的迭代方案

    原文链接:Adapting JavaScript Abstractions Over Time 译者:小溪里 校对者:郭华翔.苗冬青 即使还没有读过我的文章<在处理网络数据的 JavaScrip ...

  3. Solon详解(七)- Solon Ioc 的注解对比Spring及JSR330

    Solon详解系列文章: Solon详解(一)- 快速入门 Solon详解(二)- Solon的核心 Solon详解(三)- Solon的web开发 Solon详解(四)- Solon的事务传播机制 ...

  4. python变量及简单数据类型

    python 目录 python 1.变量 1.变量的定义 2.变量的命名 3. 关键字 4.变量的命名规则 5.变量的类型 5.不同类型变量之间的计算 6.变量的输入 7.变量的格式化输出 8.格式 ...

  5. 不再用上官网,自己部署一套ElementUI官方最新文档

    ElementUI官方的访问速度一直很慢,公司内网也无法进行外网访问.故研究了下最新的ElementUI API(2.13.2)部署教程. 先上效果图 ElementUI文档部署过程 到github下 ...

  6. Centos6.5 离线 Openssh 升级

    目录 OpenSSH 升级 一.基于 Dropbear 设置备用 ssh 服务器 二. Openssh 更新 2.1 ssh配置 备份 2.2 openssh 升级 Openssl 升级(由于Open ...

  7. matplotlib | Python强大的作图工具,让你从此驾驭图表(二)

    今天是数据处理专题的第10篇文章,我们继续来聊聊matplot这个工具库. 在上周的文章当中我们介绍了matplot的基本用法,以及展示了一些简单的例子,让大家直观地了解这个工具包.我们可以简单地将它 ...

  8. 关于px、pt、em、rem四个单位的解释

    写在前面 最近在群里突然看到一个问题,就是px pt em rem 三者的区别,这个问题看起来非常基础,也非常容易被忽略,however,面试会问到~,那我就解释一下 px px的英文是pixel,翻 ...

  9. Istio中的流量配置

    Istio中的流量配置 目录 Istio中的流量配置 Istio注入的容器 Istio-init istio-proxy Envoy架构 Pilot-agent生成的初始配置文件 Envoy管理接口获 ...

  10. web网站——nginx,LNMP部署03

    nginx功能: (1)web服务器: 默认网页目录为:/usr/share/nginx/html (2)反向代理服务器: nginx代替客户端访问后端服务器,后端服务器只知道是nginx的请求,并将 ...