一、MD5参数签名的方式

我们对api查询产品接口进行优化:

1.给app分配对应的key、secret

2.Sign签名,调用API 时需要对请求参数进行签名验证,签名方式如下:

a. 按照请求参数名称将所有请求参数按照字母先后顺序排序得到:keyvaluekeyvalue...keyvalue  字符串如:将arong=1,mrong=2,crong=3 排序为:arong=1, crong=3,mrong=2  然后将参数名和参数值进行拼接得到参数字符串:arong1crong3mrong2。

b. 将secret加在参数字符串的头部后进行MD5加密 ,加密后的字符串需大写。即得到签名Sign

新api接口代码:

app调用:http://api.test.com/getproducts?key=app_key&sign=BCC7C71CF93F9CDBDB88671B701D8A35&参数1=value1&参数2=value2.......

注:secret 仅作加密使用, 为了保证数据安全请不要在请求参数中使用。

如上,优化后的请求多了key和sign参数,这样请求的时候就需要合法的key和正确签名sign才可以获取产品数据。这样就解决了身份验证和防止参数篡改问题,如果请求参数被人拿走,没事,他们永远也拿不到secret,因为secret是不传递的。再也无法伪造合法的请求。

但是...这样就够了吗?细心的同学可能会发现,如果我获取了你完整的链接,一直使用你的key和sign和一样的参数不就可以正常获取数据了...-_-!是的,仅仅是如上的优化是不够的

请求的唯一性:

为了防止别人重复使用请求参数问题,我们需要保证请求的唯一性,就是对应请求只能使用一次,这样就算别人拿走了请求的完整链接也是无效的。
唯一性的实现:在如上的请求参数中,我们加入时间戳 :timestamp(yyyyMMddHHmmss),同样,时间戳作为请求参数之一,也加入sign算法中进行加密。

新的api接口:

app调用:
http://api.test.com/getproducts?key=app_key&sign=BCC7C71CF93F9CDBDB88671B701D8A35&timestamp=201603261407&参数1=value1&参数2=value2.......

如上,我们通过timestamp时间戳用来验证请求是否过期。这样就算被人拿走完整的请求链接也是无效的。

下面代码包含key screct生成,zuulfilter拦截校验代码。

package com.idoipo.common.message.user;

/**
* 数字签名签名模型
* Create by liping on 2019/1/9
*/
public class SignModel { //加密key
private String appKey;
//加密密钥
private String appSecret; public String getAppKey() {
return appKey;
} public void setAppKey(String appKey) {
this.appKey = appKey;
} public String getAppSecret() {
return appSecret;
} public void setAppSecret(String appSecret) {
this.appSecret = appSecret;
} @Override
public String toString() {
return "SignModel{" +
"appKey='" + appKey + '\'' +
", appSecret='" + appSecret + '\'' +
'}';
}
}
package com.idoipo.common.util;

import java.util.Stack;

/**
* Create by liping on 2019/1/9
*/
public class DecimalChange {
/**
* @return
* @version 1.0.0
* @Description 10进制转N进制
*/
public static String getDecimal(Long num, int base) {
StringBuffer sb = new StringBuffer();
String all = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
String digths = all.substring(0, base);//将要转换的进制字母对应表
//只能装字符型的栈
Stack s = new Stack();
while (num != 0) {
// digths.charAt(n % base) 返回指定索引处的值
Long bb = num % base;
s.push(digths.charAt(bb.intValue()));
num = num /base;
}
while (!s.isEmpty()) {
sb.append(s.pop());
}
return sb.toString();
} }
package com.idoipo.common.util;

import com.idoipo.common.exception.MD5UtilException;

import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; /**
* Created by liping on 2018-08-10.
*/
public class MD5Util { public static String md5(String content) throws MD5UtilException {
StringBuffer sb = new StringBuffer();
try{
MessageDigest md5 = MessageDigest.getInstance("MD5");
md5.update(content.getBytes("UTF-8"));
byte[] tmpFolder = md5.digest(); for (byte aTmpFolder : tmpFolder) {
sb.append(Integer.toString((aTmpFolder & 0xff) + 0x100, 16).substring(1));
} return sb.toString();
}catch(NoSuchAlgorithmException ex){
throw new MD5UtilException("无法生成指定内容的MD5签名", ex);
}catch(UnsupportedEncodingException ex){
throw new MD5UtilException("无法生成指定内容的MD5签名", ex);
}
} }
package com.idoipo.common.util;

import com.idoipo.common.message.user.SignModel;

import java.util.Date;
import java.util.Random; /**
* Create by liping on 2019/1/9
*/
public class AppKeyGenerate { private final static String product = "test_";
private static SignModel signModel = new SignModel();
/**
* 随机生成产品名+时间戳+1000以内随机数+16进制表示
* @return
*/
private static String getAppKey() {
Date date = new Date();
long timestamp= date.getTime();
Random random = new Random();
int randomInt1 = random.nextInt(1000);
int randomInt2 = random.nextInt(1000);
long randNum = timestamp + randomInt1 + randomInt2;
String app_key = product + DecimalChange.getDecimal(randNum,16);
return app_key;
} /**
* 根据md5加密
*
* @return
*/
public static String appSecret(String app_key) {
String mw = product + app_key;
String app_sign = MD5Util.md5(mw).toUpperCase();// 得到以后还要用MD5加密。
return app_sign;
} public static SignModel getKeySecret() {
String appKey = getAppKey();
String appSecret = appSecret(appKey);
signModel.setAppKey(appKey);
signModel.setAppSecret(appSecret);
return signModel;
} public static void main(String[] args) {
SignModel signModel = AppKeyGenerate.getKeySecret();
System.out.println(signModel);
} }

下面是过滤器拦截所有请求,只支持post

package com.idoipo.infras.gateway.api.filters.pre;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.idoipo.common.data.web.MVCResultMsg;
import com.idoipo.common.data.web.ResultCode;
import com.idoipo.common.util.AppKeyGenerate;
import com.idoipo.common.util.MD5Util;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.util.StreamUtils;
import org.springframework.util.StringUtils; import javax.servlet.http.HttpServletRequest;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.Date;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap; /**
* 第三方调用参数非法检验
*/
@Component
@SuppressWarnings("unused")
public class IllegalCheckPreFilter extends ZuulFilter {
private Logger logger = LoggerFactory.getLogger(IllegalCheckPreFilter.class); @Value("${com.idoipo.requestExpire}")
private Long requestExpire; @Override
public String filterType() {
return FilterConstants.PRE_TYPE;
} @Override
public int filterOrder() {
return FilterConstants.PRE_DECORATION_FILTER_ORDER - 4;
} @Override
public boolean shouldFilter() {
return true;
} //需要修正返回的http状态码,目前的设置无效,将setSendZuulResponse设置为false时,即可采用自定义的状态码
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
MVCResultMsg msg = new MVCResultMsg();
InputStream in;
try {
in = request.getInputStream(); String method = request.getMethod();
String interfaceMethod = request.getServletPath();
//logger.info("请求方法method={},url={}",method,interfaceMethod)
String reqBody = StreamUtils.copyToString(in, Charset.forName("UTF-8"));
if (!"POST".equals(method.toUpperCase())) {
msg.setCode(ResultCode.NOT_SUPPORT_REQUEST.getCode());
msg.setMsg(ResultCode.NOT_SUPPORT_REQUEST.getDesc());
errorMessage(ctx, msg);
return null;
} //打印请求json参数
if (!StringUtils.isEmpty(reqBody)) {
String conType = request.getHeader("content-type");
if (conType.toLowerCase().contains("application/json")) {
//默认content-type传json-->application/json
Object invokeUserObject;
JSONObject jsonObject = JSONObject.parseObject(reqBody);
Object appKey = jsonObject.get("appKey");
Object sign = jsonObject.get("sign");
Object timestamp = jsonObject.get("timestamp");
//鉴权参数为空判断
if (StringUtils.isEmpty(appKey) || StringUtils.isEmpty(sign) || StringUtils.isEmpty(timestamp)) {
msg.setCode(ResultCode.AUTHENTICATION_PARAM_MISS.getCode());
msg.setMsg(ResultCode.AUTHENTICATION_PARAM_MISS.getDesc());
errorMessage(ctx, msg);
return null;
} else {
long times = Long.valueOf(timestamp.toString());
long expireTime = times + requestExpire * 60 * 1000;
long nowDate = new Date().getTime();
//请求超过指定时间就过期,不允许调用
if (nowDate < expireTime) {
msg.setCode(ResultCode.REQUEST_REPEAT.getCode());
msg.setMsg(ResultCode.REQUEST_REPEAT.getDesc());
errorMessage(ctx, msg);
return null;
}
//对比签名,用treeMap,定义字段排序
TreeMap treeMap = new TreeMap();
treeMap.putAll(jsonObject);
Iterator iterator = treeMap.entrySet().iterator();
StringBuilder stringBuilder = new StringBuilder();
String appSecret = AppKeyGenerate.appSecret(jsonObject.get("appKey").toString());
stringBuilder.append(appSecret);
while (iterator.hasNext()) {
Map.Entry entry = (Map.Entry) iterator.next();
// 获取key
String key = (String) entry.getKey();
if (key.equals("sign")) {
continue;
}
// 获取value
String value = (String) entry.getValue();
if (StringUtils.isEmpty(value)) {
continue;
}
stringBuilder.append(key).append(value);
} if (!sign.toString().equals(signGenerate(stringBuilder))) {
msg.setCode(ResultCode.SIGN_PARAM_TAMPER.getCode());
msg.setMsg(ResultCode.SIGN_PARAM_TAMPER.getDesc());
errorMessage(ctx, msg);
} else {
ctx.setSendZuulResponse(true); //将请求往后转发
ctx.setResponseStatusCode(200);
} }
} else {
//不支持的请求类型
msg.setCode(ResultCode.NOT_SUPPORT_TRANSPORT_TYPE.getCode());
msg.setMsg(ResultCode.NOT_SUPPORT_TRANSPORT_TYPE.getDesc());
errorMessage(ctx, msg);
return null;
}
}
} catch (Exception e) {
logger.error("参数转换流异常", e);
}
return null;
} private void errorMessage(RequestContext ctx, MVCResultMsg msg) {
logger.error("MVCResultMsg={}", msg);
ctx.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
ctx.getResponse().setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
ctx.setResponseBody(new String(JSON.toJSONString(msg, SerializerFeature.WriteMapNullValue).getBytes(), Charset.forName("utf-8")));
//将结果立即返回,不再进一步操作
ctx.setSendZuulResponse(false);
} private String signGenerate(StringBuilder stringBuilder) {
String sign = MD5Util.md5(stringBuilder.toString()).toUpperCase();
return sign;
} }

springcloud提供开放api接口签名验证的更多相关文章

  1. 开放api接口签名验证

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

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

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

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

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

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

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

  5. 开放API接口安全处理!

    目录 概念 加密 MD5 Token 开放api参数 重复提交,恶意调用 日志 验证码 开放API接口安全处理! 参考文献: 公钥,私钥和数字签名这样最好理解 (转载) 概念 存在问题: 数据窃取 数 ...

  6. 开放API接口安全处理

    一.开放API接口定义 顾名思义,开放出来给其他人调用的API接口就是开放API接口.例如,短信接口.邮件接口. 二.开放API的弱点 数据窃取 用户的密码等信息被不轨之人窃取,登录账号发布敏感信息, ...

  7. 开放API接口

    [开放API]——知乎.博客园等开放API接口(更新ing)   Cnodejs.org: https://cnodejs.org/api/ 和风天气: http://docs.heweather.c ...

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

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

  9. 新浪网易淘宝等IP地区信息查询开放API接口调用方法

    通过IP地址获取对应的地区信息通常有两种方法:1)自己写程序,解析IP对应的地区信息,需要数据库.2)根据第三方提供的API查询获取地区信息. 第一种方法,参见文本<通过纯真IP数据库获取IP地 ...

随机推荐

  1. nginx.conf解读

    通常我们需要配置nginx.conf或者配置子项目的配置文件,那么我们需要解读它里面每一个参数的意义,就来个范例解读吧(有中午注释) #运行用户 user www-data; #启动进程,通常设置成和 ...

  2. 漫谈JVM之类加载机制(篇一)

    前言 最近在看一本书,发现代码里用到了Thread.currentThread().getContextClassLoader(),为什么类加载器还与线程有关系呢,为什么不直接使用ClassLoade ...

  3. PHP面向对象深入研究之【了解类】与【反射API】

    了解类 class_exists验证类是否存在 <?php // TaskRunner.php $classname = "Task"; $path = "task ...

  4. windows环境下,安装zookeeper~

    1.   概述 ZooKeeper是Hadoop的正式子项目,它是一个针对大型分布式系统的可靠协调系统,提供的功能包括:配置维护.名字服务.分布式同步.组服务等.ZooKeeper的目标就是封装好复杂 ...

  5. SSD知识

    不管什么接口的SSD,一般都由以下部分组成:主控,Flash,板,壳,品牌.下面本佬就这些部分一一发帖,仅供娱乐参考,不作任何推荐和偏向,有不同见解请直接发表,有任何错误,请直接指正,不为吵架,只为娱 ...

  6. java 高并发解决方案

    对于我们开发的网站,如果网站的访问量非常大的话,那么我们就需要考虑相关的并发访问问题了.而并发问题是绝大部分的程序员头疼的问题, 但话又说回来了,既然逃避不掉,那我们就坦然面对吧~今天就让我们一起来研 ...

  7. 微信小程序wxss设置样式

    微信小程序wxss设置样式 对于以前搞客户端开发的来说,有着客户端的逻辑,就是不知道怎么设置样式,把对应的控件显示出来 一.wxml 界面结构wxmL比较容易理解,主要是由八大类基础组件构成: 一.视 ...

  8. C#读写EXCEL(二)

    C#读写EXCEL(二) -- ::| 分类: 默认分类 | 标签: |举报 |字号大 中 小 订阅 用微信 “扫一扫” 将文章分享到朋友圈. 用易信 “扫一扫” 将文章分享到朋友圈. 下载LOFTE ...

  9. Visual Studio 2005 C# 读写Excel文件

    做作业的时候查了一点儿资料, 用的vs2k5 读 excel 发现用起来非常简单...现在编程语言没话说! 项目-添加引用-COM-Microsoft Excel 12.0 Object Librar ...

  10. leetcode897

    这道题用C++来写,在本地执行正常,但是使用OJ判断输出结果是空,暂时不清楚原因.代码如下: class Solution { public: vector<int> V; //中序遍历 ...