springcloud提供开放api接口签名验证
一、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×tamp=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接口签名验证的更多相关文章
- 开放api接口签名验证
不要急,源代码分享在最底部,先问大家一个问题,你在写开放的API接口时是如何保证数据的安全性的?先来看看有哪些安全性问题在开放的api接口中,我们通过http Post或者Get方式请求服务器的时候, ...
- 【转】开放api接口签名验证
不要急,源代码分享在最底部,先问大家一个问题,你在写开放的API接口时是如何保证数据的安全性的?先来看看有哪些安全性问题在开放的api接口中,我们通过http Post或者Get方式请求服务器的时候, ...
- 转载-常用API接口签名验证参考
原文地址: http://www.cnblogs.com/hnsongbiao/p/5478645.html 写的很好,就做个笔记了.感谢作者! 项目中常用的API接口签名验证方法: 1. 给app分 ...
- 常用API接口签名验证参考
项目中常用的API接口签名验证方法: 1. 给app分配对应的key.secret2. Sign签名,调用API 时需要对请求参数进行签名验证,签名方式如下: a. 按照请求参数名称将所有请求参数按照 ...
- 开放API接口安全处理!
目录 概念 加密 MD5 Token 开放api参数 重复提交,恶意调用 日志 验证码 开放API接口安全处理! 参考文献: 公钥,私钥和数字签名这样最好理解 (转载) 概念 存在问题: 数据窃取 数 ...
- 开放API接口安全处理
一.开放API接口定义 顾名思义,开放出来给其他人调用的API接口就是开放API接口.例如,短信接口.邮件接口. 二.开放API的弱点 数据窃取 用户的密码等信息被不轨之人窃取,登录账号发布敏感信息, ...
- 开放API接口
[开放API]——知乎.博客园等开放API接口(更新ing) Cnodejs.org: https://cnodejs.org/api/ 和风天气: http://docs.heweather.c ...
- api接口签名验证(MD5)
不要急,源代码分享在最底部,先问大家一个问题,你在写开放的API接口时是如何保证数据的安全性的?先来看看有哪些安全性问题在开放的api接口中,我们通过http Post或者Get方式请求服务器的时候, ...
- 新浪网易淘宝等IP地区信息查询开放API接口调用方法
通过IP地址获取对应的地区信息通常有两种方法:1)自己写程序,解析IP对应的地区信息,需要数据库.2)根据第三方提供的API查询获取地区信息. 第一种方法,参见文本<通过纯真IP数据库获取IP地 ...
随机推荐
- Java-Maven-Runoob:Maven 引入外部依赖
ylbtech-Java-Maven-Runoob:Maven 引入外部依赖 1.返回顶部 1. Maven 引入外部依赖 如果我们需要引入第三库文件到项目,该怎么操作呢? pom.xml 的 dep ...
- Java面试(三)
1 java中Exception 和 Error 区别 都是Throwable的子类.RuntimeException继承自Exception. Error和RuntimeException及其子类 ...
- chkdsk工具怎么修复
对于一些硬盘存储问题,即使windows自带的系统工具,也可以将其修复,比如chkdsk程序.请阅读下文,了解如何使用chkdsk来修复简单的硬盘问题. 工具/原料 windows7 chkdsk 方 ...
- VirtualBox 桥接
1.设置Virtual box,取消DHCP服务 管理->全局设定->网络->Host-Only->网络明细->DHCP服务器->启用服务器选项取消 2.宿机设置 ...
- ado connection string
Provider=SQLOLEDB.1;Password=123;Persist Security Info=True;User ID=sa;Initial Catalog=mydb;Data Sou ...
- MySQL备份还原之一mydumper
1)源码编译安装 1.下载 mydumper源码 2.解压 [mysql@localhost ~]$ tar -xvf mydumper-0.9.1.tar mydumper-0.9.1/CMakeL ...
- C#操作SQLIte数据。
using System; using System.Data; using System.Text.RegularExpressions; using System.Xml; using Syste ...
- Spark的几个问题
1.application是由driver和executor组成的,executor可以分成task,task又可以分成为stage.当一个任务提交给spark之后,spark机群的主节点会出现dri ...
- SKNode的渲染顺序
[SKNode的渲染顺序] SKNode的zPosition属性,指定相对于其父视图的z轴坐标. The default value is 0.0. The positive z axis is pr ...
- Linux 常用命令(转)
转自:https://www.cnblogs.com/gaojun/p/3359355.html 1.ls命令 就是list的缩写,通过ls 命令不仅可以查看linux文件夹包含的文件,而且可以查看文 ...