前言:服务端与前端对接的API接口,如果被第三方抓包并进行恶意篡改参数,可能会导致数据泄露和篡改数据,下面主要围绕token,签名,时间戳,三个部分来保证API接口的安全性。

强烈推荐,简单明了:

瞧瞧别人家的API接口,那叫一个优雅

1.用户成功登陆站点后,服务器会返回一个token,用户的任何操作都必须带了这个参数,可以将这个参数直接放到header里。

2.客户端用需要发送的参数和token生成一个签名sign,作为参数一起发送给服务端,服务端在用同样的方法生成sign进行检查是否被篡改。

3.但这依然存在问题,可能会被进行恶意无限制访问,这时我们需要引入一个时间戳参数,如果超时即是无效的。

4.服务端需要对token,签名,时间戳进行验证,只有token有效,时间戳未超时,签名有效才能被放行。

一、接口安全问题

  • 请求身份是否合法?

  • 请求参数是否被篡改?

  • 请求是否唯一?

开放接口

没有进行任何限制,简单粗暴的访问方式,这样的接口方式一般在开放的应用平台,查天气,查快递,只要你输入正确对应的参数调用,即可获取到自己需要的信息,我们可以任意修改参数值。

/** Description: 开放的接口
*/
@RestController
@RequestMapping("/token")
public class TokenSignController {@Autowiredprivate TokenSignService tokenSignService;@RequestMapping(value = "openDemo",method = RequestMethod.GET)public List<PersonEntity> openDemo(int personId){return tokenSignService.getPersonList(personId);}
}

二、请求身份合法性:Token认证参数

在APP开放API接口的设计中,由于大多数接口涉及到用户的个人信息以及产品的敏感数据,所以要对这些接口进行身份验证,为了安全起见让用户暴露的明文密码次数越少越好,然而客户端与服务器的交互在请求之间是无状态的,也就是说,当涉及到用户状态时,每次请求都要带上Token身份验证信息。

用户登录成功后,会获取一个ticket值,接下去任何接口的访问都需要这个参数。我们把它放置在redis内,有效期为10分钟,在ticket即将超时,无感知续命。延长使用时间,如果用户在一段时间内没进行任何操作,就需要重新登录系统。

@RequestMapping(value = "login",method = RequestMethod.POST)public JSONObject login(@NotNull String username, @NotNull String password){return tokenSignService.login(username,password);}

登录操作,查看是否有这个用户,用户名和密码匹配即可成功登录。

/** * * Description:验证登录,ticket成功后放置缓存中,* @param*/ public JSONObject login(String username,String password){JSONObject result = new JSONObject();PersonEntity personEntity = personDao.findByLoginName(username);if (personEntity == null || (personEntity != null && !personEntity.getPassword().equals(password))){result.put("success",false);result.put("ticket","");result.put("code","999");result.put("message","用户名和密码不匹配");return result;}if (personEntity.getLoginName().equals(username) && personEntity.getPassword().equals(password)){String ticket = UUID.randomUUID().toString();ticket = ticket.replace("-","");redisTemplate.opsForValue().set(ticket,personEntity.getLoginName(),10L, TimeUnit.MINUTES);result.put("success",true);result.put("ticket",ticket);result.put("code",200);result.put("message","登录成功");return result;}result.put("success",false);result.put("ticket","");result.put("code","1000");result.put("message","未知异常,请重试");return result;}

三、防止篡改:Sign签名参数

抓包工具拦截http请求后将里面的参数修改,然后再提交到后台,这就已经可以造成比较严重的后果了:比如删除一条记录,在删除过程中篡改记录id,就可以删除任何不再当前用户权限范围内的记录。

把所有的参数拼接一起,在加入系统秘钥SecretKey(前后端约定好,不能泄漏),进行MD5计算生成一个sign签名,防止参数被人恶意篡改,后台按同样的方法生成秘钥,进行签名对比。

/*** @param request* @return*/public static Boolean checkSign(HttpServletRequest request,String sign){Boolean flag= false;//检查sigin是否过期Enumeration<?> pNames =  request.getParameterNames();Map<String, String> params = new HashMap<String, String>();while (pNames.hasMoreElements()) {String pName = (String) pNames.nextElement();if("sign".equals(pName)) continue;String pValue = (String)request.getParameter(pName);params.put(pName, pValue);}System.out.println("现在的sign-->>" + sign);System.out.println("验证的sign-->>" + getSign(params,secretKeyOfWxh));if(sign.equals(getSign(params, secretKeyOfWxh))){flag = true;}return flag;}

请求携带参数TokenSign,只有拥有合法的身份Token和正确的签名Sign才能放行。这样就解决了身份验证和参数篡改问题,即使请求参数被劫持,由于获取不到SecretKey仅作本地加密使用,不参与网络传输),无法伪造合法的请求。


四、重放攻击:时间戳参数

4.1、重放攻击

Sign签名参数虽然解决了请求参数被篡改的隐患,但是还存在着重复使用请求参数伪造二次请求的隐患。举个例子,用户发起一个付款请求,请求中带着付款参数,攻击者拦截了该请求,向服务器重复的发送该请求,如果服务器端没有配置防止重放攻击策略,则可能会进行多次付款,造成用户损失。

4.2、引入timestamp参数

引入一个时间戳参数,保证接口仅在一分钟内有效,需要和客户端时间保持一致。

public static long getTimestamp()
{long timestampLong = System.currentTimeMillis();long timestampsStr = timestampLong / 1000;return timestampsStr;
}

在请求中增加 timestamp 参数要来表示请求时间戳,服务方端接收该请求后,根据当前时间生成一个接收时间戳,然后根据两个时间戳的差值进行请求判定,如果差值大于指定的阈值,则认为请求无效,否则请求通过。关于阈值的选定,可以根据接口的响应速度进行适当的调整,一般默认为 60 秒。

//需要跟当前服务器时间进行对比,如果超过一分钟,就拒绝本次请求,节省服务器查询的请求消耗
if((接收时间戳-请求时间戳) > 60)
{"请求失败"
} else
{"请求通过"
}

该方案要求请求和响应的双方必须进行时间同步,如果服务的双方本身存在时间上的差异,会对差值的计算产生影响,最后导致请求的判定产生偏差。

缺点:通过上面的判定逻辑可以发现,在小于阈值的时间段内是可以进行重复请求的,该方案不能保证请求仅一次有效。

4.3、Redis或者Guava缓存保证请求仅一次有效

引入timestamp参数不能保证请求仅一次有效,此时可以引入Redis分布式缓存或者Guava本地缓存来解决。

在请求中增加一个通过指定规则产生的 token,标识请求的唯一性,服务方接收该请求后,先判断缓存集合中是否存在该 token,如果存在则认为此次请求无效,否则将 token 放入缓存中,通过请求通过。

伪代码如下:

if(token 存在于缓存集合中)
{"请求失败"
} else
{将 token 放入集合中"请求通过"
}

4.4、timestamp 和 缓存 结合优化方案

加时间戳判断主要是为了减少缓存查询的次数,超时的请求直接就返回了。

if((接收时间戳-请求时间戳) > 60秒){"请求失败"
} if(token 存在于缓存集合中){"请求失败"
} else {将 token 放入集合中,缓存时间60秒"请求通过"
}

五、拦截器

每次请求都带有这三个参数,我们都需要进行验证,只有在三个参数都满足我们的要求,才允许数据返回或被操作。

public class LoginInterceptor implements HandlerInterceptor {@Autowiredprivate RedisTemplate redisTemplate;@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response,Object handler) throws IOException {JSONObject jsonObject = new JSONObject();String ticket = request.getParameter("ticket");String sign = request.getParameter("sign");String ts = request.getParameter("ts");if (StringUtils.isEmpty(ticket) || StringUtils.isEmpty(sign) || StringUtils.isEmpty(ts)){jsonObject.put("success",false);jsonObject.put("message","args is isEmpty");jsonObject.put("code","1001");PrintWriter printWriter = response.getWriter();printWriter.write(jsonObject.toJSONString());return false;}//如果redis存在ticket就认为是合法的请求if (redisTemplate.hasKey(ticket)){System.out.println(redisTemplate.opsForValue().getOperations().getExpire(ticket));String values = (String) redisTemplate.opsForValue().get(ticket);//判断ticket是否即将过期,进行续命操作if (redisTemplate.opsForValue().getOperations().getExpire(ticket) != -2 && redisTemplate.opsForValue().getOperations().getExpire(ticket) < 20){redisTemplate.opsForValue().set(ticket,values,10L, TimeUnit.MINUTES);}System.out.println(SignUtils.getTimestamp());//判断是否重复访问,存在重放攻击的时间窗口期if (SignUtils.getTimestamp() - Long.valueOf(ts) > 600){jsonObject.put("success",false);jsonObject.put("message","Overtime to connect to server");jsonObject.put("code","1002");PrintWriter printWriter = response.getWriter();printWriter.write(jsonObject.toJSONString());return false;}//验证签名if (!SignUtils.checkSign(request,sign)){jsonObject.put("success",false);jsonObject.put("message","sign is invalid");jsonObject.put("code","1003");PrintWriter printWriter = response.getWriter();printWriter.write(jsonObject.toJSONString());return false;}return true;}else {jsonObject.put("success",false);jsonObject.put("message","ticket is invalid,Relogin.");jsonObject.put("code","1004");PrintWriter printWriter = response.getWriter();printWriter.write(jsonObject.toJSONString());}return false;}
}

开发API接口的安全验证:token,参数签名,时间戳相关推荐

  1. php接口开发 安全_PHP开发api接口安全验证的实例讲解

    php的api接口 在实际工作中,使用PHP写api接口是经常做的,PHP写好接口后,前台就可以通过链接获取接口提供的数据,而返回的数据一般分为两种情况,xml和json,在这个过程中,服务器并不知道 ...

  2. web api接口开发实例_C# 物联网开发API接口系列(4)

    C# 物联网开发API接口系列(四) #久爱物联网#MQTT# [提纲] 1)获取设备列表(某用户的) 2)单独检查设备是否存在合法 [正文] /// /// 获取设备列表 /// /// 令牌 // ...

  3. API接口之JWT设置token过期时间(二)

    目录 1.什么是Jwt 2.token是什么 3.为什么要使用token 4.如何实现token 5.JWT的简单案例 6.API接口token案例 6.1 token的创建 6.2 用户验证流程 7 ...

  4. php api查询开发,PHP开发API接口(注册、登录、查询用户信息)的实例代码

    本节主要内容: php开发API接口的实现代码 一.PHP API接口的服务端部分 复制代码 代码示例: /** * PHP开发API接口 服务端 * edit: www.jbxue.com */ r ...

  5. 个人微信号二次开发sdk协议,微信个人号开发API接口

    个人微信号二次开发sdk协议,微信个人号开发API接口 微信SDK程序概要说明 个人微信号开发sdk非微信ipad协议.非mac协议,非安卓协议,api可实现微信99%功能: 无需扫码登录.可收发朋友 ...

  6. php magento 开发,magento2 开发Api接口实例

    magento2 开发Api接口实例 新建一个模块 第一步先创建module.xml初始化模块 模块配置 – etc/module.xml 然后新建Registration 注册模块 – regist ...

  7. 技术小白如何快速开发API接口?

    API接口是什么? API全称是:Application Programming Interface,即:应用程序接口,是一些预先定义的函数,或指软件系统不同组成部分衔接的约定.开发人员可以使用这些A ...

  8. 智表ZCELL产品V1.4.0开发API接口文档 与 产品功能清单

    为了方便大家使用ZCELL,应网友要求,整理编写了相关文档,现与产品一起同步发布,供大家下载使用,使用过程中如有疑问,请与我QQ联系. 智表(ZCELL)V1.4.0版本  功能清单文档下载地址: 功 ...

  9. php api查询开发,PHP 开发API接口 登记,登录,查询用户资料

    PHP 开发API接口 注册,登录,查询用户资料 服务端 0) { exit(json_encode(1)); //返回1表示注册失败 } else { $addsql = "insert ...

最新文章

  1. (PASS)JAVA数组去重 三种方法 (不用集合)
  2. 10、单机运行环境搭建之 --Windows下mysqldump 备份与还原数据库
  3. easyui打开新的选项卡_Easyui Tabs 标签页/选项卡_EasyUI 插件
  4. 结构型模式—外观模式
  5. Flutter入门:Hero共享元素
  6. 【百度地图API】暑假放假回老家——城市切换功能
  7. 时空上下文视觉跟踪(STC)
  8. 在C#代码中应用Log4Net(五)将Log4Net正确地封装在自己的类库中并进行调用
  9. 【转】:andriod 开发环境工具下载地址(2)
  10. C++std命名空间和头文件详解
  11. highscore软件_软件|标准物质PDF卡片查找HighScore
  12. Eclipse EGit插件安装使用记录
  13. 宅在家里写数据库中联表查询
  14. 谁将称霸跨平台应用市场?
  15. 基于SSM的旅游信息平台
  16. 软件需求分析模板2020-11-04
  17. 拼多多API详情接口调用示例
  18. spring cloud SnakeYAML RCE复现
  19. windows补丁修复
  20. analy32.xll下载_Android Studio 4.0添加了Motion Editor和Build Analyzer

热门文章

  1. 红帽RHCE培训-课程1笔记目录
  2. [unreal] 寻路:随机漫游的实现
  3. 书真是太神奇了的超级玛丽
  4. 干货 | 携程机票大数据架构最佳实践
  5. 小米盒子/电视如何安装影视通APP?
  6. AndroidUtils:Android开发不得不收藏的Utils
  7. linux小知识点记录
  8. 希尔伯特曲线 java_《算法心得:高效算法的奥秘》PDF 下载
  9. “需要Mi crosoft .NET Framework 3.5 Service Pack 1”规则失败。
  10. read函数 读取指定长度的数据