前言

后端在写对外的API接口时,一般会对参数进行签名来保证接口的安全性,在设计签名算法的时候,主要考虑的是这几个问题:
1. 请求的来源是否合法
2. 请求参数是否被篡改
3. 请求的唯一性
我们的签名加密也是主要针对这几个问题来实现

设计

基于上述的几个问题,我们来通过已下步骤来实现签名加密:
1. 通过分配给APP对应的app_key和app_secret来验证身份
2. 通过将请求的所有参数按照字母先后顺序排序后拼接再MD5加密老保证请求参数不被篡改
3. 请求里携带时间戳参数老保证请求的唯一和过期,重复的请求在指定时间(可配置)内有效

实现

  1. 签名生成:

    1. 生成当前时间戳timestamp=now
    2. 按照请求参数名的字母升序排列非空请求参数(包含accessKey)

      stringA="AccessKey=access&home=world&name=hello&work=java&timestamp=now&nonce=random";
    3. 拼接密钥accessSecret

      stringSignTemp="AccessKey=access&home=world&name=hello&work=java&timestamp=now&nonce=random&accessSecret=secret";
    4. MD5并转换为大写生成签名

      sign=MD5(stringSignTemp).toUpperCase();

JAVA代码如下:params是从request里面获取的所有参数map,accessSecret是加密密钥

 private String createSign(Map<String, Object> params, String accessSecret) throws UnsupportedEncodingException {
Set<String> keysSet = params.keySet();
Object[] keys = keysSet.toArray();
Arrays.sort(keys);
StringBuilder temp = new StringBuilder();
boolean first = true;
for (Object key : keys) {
if (first) {
first = false;
} else {
temp.append("&");
}
temp.append(key).append("=");
Object value = params.get(key);
String valueString = "";
if (null != value) {
valueString = String.valueOf(value);
}
temp.append(valueString);
}
temp.append("&").append(ACCESS_SECRET).append("=").append(accessSecret);
return MD5Util.MD52(temp.toString()).toUpperCase();
}
  1. 签名校验:

拦截器部分代码

  • 参数格式校验
  • 超时校验
  • 验证签名
  public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        Map<String, Object> result = new HashMap<String, Object>();
String timestamp = request.getParameter(TIMESTAMP_KEY);
String accessKey = request.getParameter(ACCESS_KEY);
String accessSecret = map.get(accessKey); if (!org.apache.commons.lang.StringUtils.isNumeric(timestamp)) {
result.put("code", 1000);
result.put("msg", "请求时间戳不合法");
WebUtils.writeJsonByObj(result, response, request);
return false;
} // 检查KEY是否合理
if (StringUtils.isEmpty(accessKey) || StringUtils.isEmpty(accessSecret)) {
result.put("code", 1001);
result.put("msg", "加密KEY不合法");
WebUtils.writeJsonByObj(result, response, request);
return false;
}
Long ts = Long.valueOf(timestamp);
// 禁止超时签名
if (System.currentTimeMillis() - ts > SIGN_EXPIRED_TIME) {
result.put("code", 1002);
result.put("msg", "请求超时");
WebUtils.writeJsonByObj(result, response, request);
return false;
} if (!verificationSign(request, accessKey, accessSecret)) {
result.put("code", 1003);
result.put("msg", "签名错误");
WebUtils.writeJsonByObj(result, response, request);
return false;
}
return true;
}

校验签名

 private boolean verificationSign(HttpServletRequest request, String accessKey, String accessSecret) throws UnsupportedEncodingException {
Enumeration<?> pNames = request.getParameterNames();
Map<String, Object> params = new HashMap<String, Object>();
while (pNames.hasMoreElements()) {
String pName = (String) pNames.nextElement();
if (SIGN_KEY.equals(pName)) continue;
Object pValue = request.getParameter(pName);
params.put(pName, pValue);
}
String originSign = request.getParameter(SIGN_KEY);
String sign = createSign(params, accessSecret);
return sign.equals(originSign);
}
  1. 完整代码:

这里通过拦截器来实现接口拦截,可自行替换

package com.mlcs.mop.common.web.interceptor;

import com.mlcs.core.conf.ZKClient;
import com.mlcs.mop.common.web.util.MD5Util;
import com.mlcs.mop.common.web.util.WebUtils;
import org.apache.zookeeper.KeeperException;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap; /**
* Author: Kelin
* Date: 2018/5/16
* Description:
*/
@SuppressWarnings("SuspiciousMethodCalls")
public class SimpleApiSignInterceptor extends HandlerInterceptorAdapter { // 签名超时时长,默认时间为5分钟,ms
private static final int SIGN_EXPIRED_TIME = 5 * 60 * 1000; private static final String API_SIGN_KEY_CONFIG_PATH = "/mop/common/system/api_sign_key_mapping.properties"; private static final String SIGN_KEY = "sign"; private static final String TIMESTAMP_KEY = "timestamp"; private static final String ACCESS_KEY = "accessKey"; private static final String ACCESS_SECRET = "accessSecret"; private static Map<String, String> map = new ConcurrentHashMap<String, String>(); static {
// 从zk加载key映射到内存里面
try {
String data = ZKClient.get().getStringData(API_SIGN_KEY_CONFIG_PATH);
Properties properties = new Properties();
properties.load(new StringReader(data));
for (Object key : properties.keySet()) {
map.put(String.valueOf(key), properties.getProperty(String.valueOf(key)));
}
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} } @Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { Map<String, Object> result = new HashMap<String, Object>();
String timestamp = request.getParameter(TIMESTAMP_KEY);
String accessKey = request.getParameter(ACCESS_KEY);
String accessSecret = map.get(accessKey); if (!org.apache.commons.lang.StringUtils.isNumeric(timestamp)) {
result.put("code", 1000);
result.put("msg", "请求时间戳不合法");
WebUtils.writeJsonByObj(result, response, request);
return false;
} // 检查KEY是否合理
if (StringUtils.isEmpty(accessKey) || StringUtils.isEmpty(accessSecret)) {
result.put("code", 1001);
result.put("msg", "加密KEY不合法");
WebUtils.writeJsonByObj(result, response, request);
return false;
}
Long ts = Long.valueOf(timestamp);
// 禁止超时签名
if (System.currentTimeMillis() - ts > SIGN_EXPIRED_TIME) {
result.put("code", 1002);
result.put("msg", "请求超时");
WebUtils.writeJsonByObj(result, response, request);
return false;
} if (!verificationSign(request, accessKey, accessSecret)) {
result.put("code", 1003);
result.put("msg", "签名错误");
WebUtils.writeJsonByObj(result, response, request);
return false;
}
return true;
} private boolean verificationSign(HttpServletRequest request, String accessKey, String accessSecret) throws UnsupportedEncodingException {
Enumeration<?> pNames = request.getParameterNames();
Map<String, Object> params = new HashMap<String, Object>();
while (pNames.hasMoreElements()) {
String pName = (String) pNames.nextElement();
if (SIGN_KEY.equals(pName)) continue;
Object pValue = request.getParameter(pName);
params.put(pName, pValue);
}
String originSign = request.getParameter(SIGN_KEY);
String sign = createSign(params, accessSecret);
return sign.equals(originSign);
} private String createSign(Map<String, Object> params, String accessSecret) throws UnsupportedEncodingException {
Set<String> keysSet = params.keySet();
Object[] keys = keysSet.toArray();
Arrays.sort(keys);
StringBuilder temp = new StringBuilder();
boolean first = true;
for (Object key : keys) {
if (first) {
first = false;
} else {
temp.append("&");
}
temp.append(key).append("=");
Object value = params.get(key);
String valueString = "";
if (null != value) { valueString = String.valueOf(value);
}
temp.append(valueString);
}
temp.append("&").append(ACCESS_SECRET).append("=").append(accessSecret);
return MD5Util.MD52(temp.toString()).toUpperCase();
}
}

简单API接口签名验证的更多相关文章

  1. 转载-常用API接口签名验证参考

    原文地址: http://www.cnblogs.com/hnsongbiao/p/5478645.html 写的很好,就做个笔记了.感谢作者! 项目中常用的API接口签名验证方法: 1. 给app分 ...

  2. 常用API接口签名验证参考

    项目中常用的API接口签名验证方法: 1. 给app分配对应的key.secret2. Sign签名,调用API 时需要对请求参数进行签名验证,签名方式如下: a. 按照请求参数名称将所有请求参数按照 ...

  3. 开放api接口签名验证

    不要急,源代码分享在最底部,先问大家一个问题,你在写开放的API接口时是如何保证数据的安全性的?先来看看有哪些安全性问题在开放的api接口中,我们通过http Post或者Get方式请求服务器的时候, ...

  4. api接口签名验证(MD5)

    不要急,源代码分享在最底部,先问大家一个问题,你在写开放的API接口时是如何保证数据的安全性的?先来看看有哪些安全性问题在开放的api接口中,我们通过http Post或者Get方式请求服务器的时候, ...

  5. 【转】开放api接口签名验证

    不要急,源代码分享在最底部,先问大家一个问题,你在写开放的API接口时是如何保证数据的安全性的?先来看看有哪些安全性问题在开放的api接口中,我们通过http Post或者Get方式请求服务器的时候, ...

  6. springcloud提供开放api接口签名验证

    一.MD5参数签名的方式 我们对api查询产品接口进行优化: 1.给app分配对应的key.secret 2.Sign签名,调用API 时需要对请求参数进行签名验证,签名方式如下: a. 按照请求参数 ...

  7. API接口签名验证2

    http://www.jianshu.com/p/d47da77b6419 系统从外部获取数据时,通常采用API接口调用的方式来实现.请求方和�接口提供方之间的通信过程,有这几个问题需要考虑: 1.请 ...

  8. PHP 开发API接口签名验证

    就安全来说,所有客户端和服务器端的通信内容应该都要通过加密通道(HTTPS)传输,明文的HTTP通道将会是man-in-the- middle及其各种变种攻击的温床.所谓man-in-the-midd ...

  9. api接口签名验证

    由于http是无状态的,所以正常情况下在浏览器浏览网页,服务器都是通过访问者的cookie(cookie中存储的jsessionid)来辨别客户端的身份的,当客户端进行登录服务器也会将登录信息存放在服 ...

随机推荐

  1. 三步完成Source Insight 4.0 破解安装(转)

    转自:https://blog.csdn.net/biubiuibiu/article/details/78044232 三步完成Source Insight 4.0 破解安装   下载地址有更新,之 ...

  2. TextView点击后背景颜色、文字颜色改变(转)

    转自:http://blog.csdn.net/u013278940/article/details/51152655 TextView本没有点击效果,故为实现点击后文字颜色改变和背景改变,需要写se ...

  3. HBase性能优化方法总结(三):读表操作(转)

    转自:http://www.cnblogs.com/panfeng412/archive/2012/03/08/hbase-performance-tuning-section3.html 本文主要是 ...

  4. NORDIC内核ARM蓝牙芯片NRF51802/NRF51822

    Nordic  nRF51 系列的IC 和协议堆栈对内存大小.封装类型.接口.周边产品及无线连接提供更多选择. 关于 nRF51 系列 多协议 2.4GHz 射频收发器拥有高性能.超低功耗以及灵活性等 ...

  5. UVa 699 The Falling Leaves (树水题)

    Each year, fall in the North Central region is accompanied by the brilliant colors of the leaves on ...

  6. paper 135:关于C#泛型的知识点

    计划着要用一个月的时间把  C#语言Windows程序设计 搞定,现在是零零散散的知识点,日积月累吧!朋友们,看这里咯~呵呵 原文地址:http://www.blogjava.net/Jack2007 ...

  7. 根据一个经纬度坐标获取周边最近经纬。Java实现

    1.需求:指定一个经纬度坐标,如:(31.2121751783,121.4411213954).周围有一堆经纬度坐标,找出与它最近的那个. 2.实现思路:将给出经纬度看成原点(0,0).周围经纬度定位 ...

  8. python 线程,进程与协程

    引言 线程 创建普通多线程 线程锁 互斥锁 信号量 事件 条件锁 定时器 全局解释器锁 队列 Queue:先进先出队列 LifoQueue:后进先出队列 PriorityQueue:优先级队列 deq ...

  9. jekins—持续集成

    json转换为Python的字典形式 Martin fowler:通过自动化的构建,编译-发布-自动化测试,尽早的发现集成的错误 持续集成的要素: 统一的代码库 自动构建编译 自动测试(单元测试) 每 ...

  10. (转)将SVN从一台服务器迁移到另一台服务器(Windows Server VisualSVN Server)

    转:http://blog.sina.com.cn/s/blog_855a24030102xp9q.html 服务器环境: Windows Server 2012  软件版本: VisualSVN-S ...