一、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. Annotation之一:Java Annotation基本功能介绍

    一.元数据的作用 如果要对于元数据的作用进行分类,目前还没有明确的定义,不过我们可以根据它所起的作用,大致可分为三类: 编写文档:通过代码里标识的元数据生成文档.这是最常见的,也是java 最早提供的 ...

  2. Py修行路 python基础 (三)字典

    names=["zhang"]names2=["jack","jie"]names.clear()print(names) #清空整个列表 ...

  3. 初识 Julia

    Ubuntu 下安装 Julia 环境 sch01ar@ubuntu:~$ sudo apt install julia 安装完成后打开 Julia 的交互式会话 sch01ar@ubuntu:~$ ...

  4. Linux - 对文件和目录的权限管理

    对文件的权限管理 ls -l,也可以用 ll 命令查看文件权限的相关信息 第一列“-rw-r--r--.”为权限信息,权限信息的最后一个点表示为在安全环境下创建的 第二列“1”为硬链接数,第三列“ro ...

  5. python----python使用mysql

    Python操作MySQL主要使用两种方式: 原生模块 pymsql ORM框架 SQLAchemy pymql pymsql是Python中操作MySQL的模块,在windows中的安装: pip ...

  6. 定时器Quartz ClassNotFound org.springframework.scheduling.quartz.SchedulerFactoryBean

    转自:https://blog.csdn.net/truong/article/details/37508003 没有添加spring-context-support-3.2.4.RELEASE.ja ...

  7. Hibenate错误汇总:java.lang.NoClassDefFoundError: org/jboss/logging/BasicLogger

    转自:https://bioubiou.iteye.com/blog/1769950 1 Hibenate异常汇总:java.lang.NoClassDefFoundError: org/jboss/ ...

  8. 解析Java反射 - invoke方法

    最近工作中涉及到获取同程火车票,大概描述为:将本地获取的发出城市,目的城市及出发时间按固定格式封装,调用接口获取可乘坐座席等级最高的火车票,接口返回数据用包含三层类封装的类接受,接受的类总共为四层,倒 ...

  9. url的进行传参拼接

    在项目中会遇到把这一个页面的参数传到下一页里面,这里我在项目中用到一个例证(大神就绕过吧嘻嘻):url: '/pages/buy/submitOrder/submitOrder?sku_id=' + ...

  10. C51串口的SCON寄存器及工作…

    原文地址:C51串口的SCON寄存器及工作方式作者:batistar 一,串行口控制寄存器SCON 它用于定义串行口的工作方式及实施接收和发送控制.字节地址为98H,其各位定义如下表: D7 D6 D ...