简单API接口签名验证
前言
后端在写对外的API接口时,一般会对参数进行签名来保证接口的安全性,在设计签名算法的时候,主要考虑的是这几个问题:
1. 请求的来源是否合法
2. 请求参数是否被篡改
3. 请求的唯一性
我们的签名加密也是主要针对这几个问题来实现
设计
基于上述的几个问题,我们来通过已下步骤来实现签名加密:
1. 通过分配给APP对应的app_key和app_secret来验证身份
2. 通过将请求的所有参数按照字母先后顺序排序后拼接再MD5加密老保证请求参数不被篡改
3. 请求里携带时间戳参数老保证请求的唯一和过期,重复的请求在指定时间(可配置)内有效
实现
签名生成:
- 生成当前时间戳timestamp=now
- 按照请求参数名的字母升序排列非空请求参数(包含accessKey)
stringA="AccessKey=access&home=world&name=hello&work=java×tamp=now&nonce=random";
- 拼接密钥accessSecret
stringSignTemp="AccessKey=access&home=world&name=hello&work=java×tamp=now&nonce=random&accessSecret=secret";
- 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();
}
- 签名校验:
拦截器部分代码
- 参数格式校验
- 超时校验
- 验证签名
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);
}
- 完整代码:
这里通过拦截器来实现接口拦截,可自行替换
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接口签名验证的更多相关文章
- 转载-常用API接口签名验证参考
原文地址: http://www.cnblogs.com/hnsongbiao/p/5478645.html 写的很好,就做个笔记了.感谢作者! 项目中常用的API接口签名验证方法: 1. 给app分 ...
- 常用API接口签名验证参考
项目中常用的API接口签名验证方法: 1. 给app分配对应的key.secret2. Sign签名,调用API 时需要对请求参数进行签名验证,签名方式如下: a. 按照请求参数名称将所有请求参数按照 ...
- 开放api接口签名验证
不要急,源代码分享在最底部,先问大家一个问题,你在写开放的API接口时是如何保证数据的安全性的?先来看看有哪些安全性问题在开放的api接口中,我们通过http Post或者Get方式请求服务器的时候, ...
- api接口签名验证(MD5)
不要急,源代码分享在最底部,先问大家一个问题,你在写开放的API接口时是如何保证数据的安全性的?先来看看有哪些安全性问题在开放的api接口中,我们通过http Post或者Get方式请求服务器的时候, ...
- 【转】开放api接口签名验证
不要急,源代码分享在最底部,先问大家一个问题,你在写开放的API接口时是如何保证数据的安全性的?先来看看有哪些安全性问题在开放的api接口中,我们通过http Post或者Get方式请求服务器的时候, ...
- springcloud提供开放api接口签名验证
一.MD5参数签名的方式 我们对api查询产品接口进行优化: 1.给app分配对应的key.secret 2.Sign签名,调用API 时需要对请求参数进行签名验证,签名方式如下: a. 按照请求参数 ...
- API接口签名验证2
http://www.jianshu.com/p/d47da77b6419 系统从外部获取数据时,通常采用API接口调用的方式来实现.请求方和�接口提供方之间的通信过程,有这几个问题需要考虑: 1.请 ...
- PHP 开发API接口签名验证
就安全来说,所有客户端和服务器端的通信内容应该都要通过加密通道(HTTPS)传输,明文的HTTP通道将会是man-in-the- middle及其各种变种攻击的温床.所谓man-in-the-midd ...
- api接口签名验证
由于http是无状态的,所以正常情况下在浏览器浏览网页,服务器都是通过访问者的cookie(cookie中存储的jsessionid)来辨别客户端的身份的,当客户端进行登录服务器也会将登录信息存放在服 ...
随机推荐
- docker 数据持久化
confluence #!/bin/bash docker run \ --name confluence \ --volume "$PWD/data/opt":/opt \ -- ...
- Mybatis基于XML配置SQL映射器(一)
Durid和Mybatis开发环境搭建 SpringBoot搭建基于Spring+SpringMvc+Mybatis的REST服务(http://www.cnblogs.com/nbfujx/p/76 ...
- thinkphp生成二维码
/** * 生成二维码 * @param string $url url连接 * @param integer $size 尺寸 纯数字 */ function qrcode($url,$size=4 ...
- 探索Redis设计与实现11:使用快照和AOF将Redis数据持久化到硬盘中
本文转自互联网 本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutorial ...
- python中匿名函数lambda如何用
python中经常用到的一个函数:匿名函数lambda ,什么是匿名函数?匿名函数的意义是什么?匿名函数怎么样用?(疑问三连,what,why,how) 一,什么是匿名函数? python中没有名字的 ...
- sync.Once.Do(f func())
sync.Once.Do(f func())是一个挺有趣的东西,能保证once只执行一次,无论你是否更换once.Do(xx)这里的方法,这个sync.Once块只会执行一次. package mai ...
- getjob
[op@TIM getpage]$ cat job.py #coding: utf- #title..href... import urllib.request import time url=[ p ...
- visual studio code -- python
录: 前提: 已安装python 在vsc中安装pthon模块 快速入门 打开控制台(ctrl+shift+P):Python: Select Interpreter,选择python解释器 或者在软 ...
- 使用JQuery对页面进行绑值
使用JQuery对页面进行绑值 <!DOCTYPE html> <html> <head> <meta charset="UTF-8"&g ...
- java %d %n \n
Java中,%d和%f分别用来表示输出时,替换整型输出和浮点型输出的占位符. 如: int a=28; float b = 13.0f; System.out.printf("整数是:%d% ...