简单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)来辨别客户端的身份的,当客户端进行登录服务器也会将登录信息存放在服 ...
随机推荐
- 力扣 ——4Sum (四数之和)python 实现
题目描述: 中文: 给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 targe ...
- JAVA读取Excel2003、2007、2010教程
import java.io.File;import java.io.FileInputStream;import org.apache.poi.ss.usermodel.Row;import org ...
- 【串线篇】MyBatis简介
一.MyBatis 和数据库进行交互:持久化层框架(SQL映射框架): 1).纯手工 从原始的JDBC----dbutils(QueryRunner)-------JdbcTemplate----xx ...
- typedef 函数指针的使用(含例子)
C代码 //#include<iostream.h> #include<stdio.h> typedef int (*FP_CALC)(int, int); ...
- Selenium之Android使用学习
20140507 Selenium一般用在web自动化上,为什么Android上也能用呢? 如图,手机端和DB联动:手机端的客户端给server发数据流,进行增删改查操作,这种写数据用update更新 ...
- 漫谈C语言结构体
相信大家对于结构体都不陌生.在此,分享出本人对C语言结构体的学习心得.如果你发现这个总结中有你以前所未掌握的,那本文也算是有点价值了.当然,水平有限,若发现不足之处恳请指出.代码文件test.c我放在 ...
- 阿里云epel源
epel是个好东西,不过国外的速度实在是不能忍受.所以 有了这篇文章.1. 首先卸载以前装的epel以免影响 rpm -e epel-release 2. 下载阿里提供的epel wget -P /e ...
- yum安装apache
一.查询是否已经安装apache rpm -qa httpd 注:Apache在linux系统里的名字是httpd 如果有返回的信息,则会显示已经安装的软件.如果没有则不会显示其它的信息.如下图是 ...
- 使用vue-cli3时怎么mock数据
应用场景 在前后端分离的开发模式中,后端给前端提供一个接口,由前端向后端发请求,得到数据后前端进行渲染. 由于前后端开发进度的不统一,前端往往使用本地的测试数据进行数据渲染的测试. 如何配置 在vue ...
- jquery 条件搜索某个标签下的子标签
$("li[name='"+name+"']").find("a[value='" + value + "']").pa ...