互联网通用架构技术----公网API安全规范

基于公网的http协议的请求/响应时不安全的,存在被截获,篡改,重发的可能。

Restful API接口要求

  • 防伪装攻击(如:公网,第三方有意或恶意的调用接口);

  • 防篡改攻击(如:公网,请求头/查询字符串/内容,在传输过程中被篡改);

  • 防重发攻击(如:公网,请求被截获,稍后被重发多次);

  • 防数据信息泄露(如:截获用户请求,截获到账号,密码等);

基于HTTP协议的接口(通用数据交互接口)需要满足前三条要求(主要通过时间戳和签名),第四条采用HTTPS协议或传输内容使用非对称加密(用在用户登录,授权,解密接口)。

基于内网的API

通过签名,IP白名单搞定。

参考HTTPS方案

参考OAuth授权机制

基于HTTP方案

设计原则:
  • 轻量级;

  • 适合于异构系统(跨操作系统,多语言简易实现);

  • 易于开发;

  • 易于测试;

  • 易于部署;

  • 满足接口安全要求,无过度设计;

接口签名参考:
  • _appid:调用方身份ID,接口提供方用此来识别不同的调用者;
  • _sign:一次接口调用签名值(随机),服务器端“防止 伪装请求/防篡改/防重发”识别的主要依据;
  • _timestamp:时间戳,允许容错+-10分钟,用于识别过期调用,同时用于签名的盐值,获取当前时间戳,需要调用统一时间戳接口;
签名算法过程:
  • 对除签名外的所有请求参数按key做升序排列,value无需编码, (假设当前时间的时间戳是12345678) 例如:有c=3,b=2,a=1 三个参,另加上时间戳后, 按key排序后为:a=1,b=2,c=3,_timestamp=12345678。 ;

  • 把参数名和参数值链接组成字符串, a1b2c3_timestamp12345678;

  • 用申请得到的appkey链接到拼接字符串的头部和尾部,然后进行32位md5加密,最后得到md5加密摘要转换成大写, 示例:假设appkey=test,md5(testa1b2c3_timestamp12345678test),取得MD5摘要值 C5F3EB5D7DC2748AED89E90AF00081E6 。 ;

代码实现:

签名生成

签名生成

C#实现

JAVA实现

认证过程
  1. 用户发送用户名/密码到服务端,服务端通过DB验证用户名/密码,验证成功返回 约定算法加密(Hash(随机码[随机盐])+签名)到客户端;
  2. 客户端通过约定算法解密 加入随机盐,hash随机盐与服务端签名对比,通过则认证,返回客户端 hash(随机盐)+签名到服务端;
  3. 公钥 = 客户端/服务端约定的算法;私钥 = 通过对方提供的随机数可以解密与签名对比,得到签名值;
Java代码示例
/**
 * Description
 * 签名工具
 *
 * @author Mr. Chun.
 */
public class SignBuilder {

    /**
     * 生成签名结果
     *
     * @param sArray 要签名的数组
     * @return 签名结果字符串
     */
    public static String buildMysign(Map<String, String> sArray, String secret) {
        String prestr = createLinkString(sArray); //把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串
        prestr = secret+ prestr + secret; //把拼接后的字符串再与安全校验码直接连接起来

        String mysign;
        try {
            mysign = DigestUtils.md5Hex(prestr.getBytes("UTF-8"));
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("MD5签名过程中出现错误,指定的编码集错误");
        }

        return mysign;
    }

    /**
     * 除去数组中的空值和签名参数
     *
     * @param sArray 签名参数组
     * @return 去掉空值与签名参数后的新签名参数组
     */
    private static Map<String, String> paraFilter(Map<String, String> sArray) {

        Map<String, String> result = new HashMap<String, String>();

        if (sArray == null || sArray.size() <= 0) {
            return result;
        }

        for (String key : sArray.keySet()) {
            String value = sArray.get(key);
            if (value == null || value.equals("") || key.equalsIgnoreCase("sign") || key.equalsIgnoreCase("mobileDevice") || key.equals("v")) {
                continue;
            }
            result.put(key, value);
        }

        return result;
    }

    /**
     * 把数组所有元素排序,并按照“参数=参数值”的模式用“&”字符拼接成字符串
     *
     * @param params 需要排序并参与字符拼接的参数组
     * @return 拼接后字符串
     */
    public static String createLinkString(Map<String, String> params) {
        params = paraFilter(params);
        List<String> keys = new ArrayList<String>(params.keySet());
        Collections.sort(keys);

        String prestr = "";

        for (int i = 0; i < keys.size(); i++) {
            String key = keys.get(i);
            String value = params.get(key);

//            if (i == keys.size() - 1) {//拼接时,不包括最后一个&字符
//                prestr = prestr + key + "=" + value;
//            } else {
//                prestr = prestr + key + "=" + value + "&";
//            }

            prestr = prestr + key + value;
        }

        return prestr;
    }

    /**
     * 做map的转换
     *
     * @param parameterMap
     * @return
     */
    public static Map<String, String> convertRequestMap(Map<String, String[]> parameterMap) {
        Map<String, String> alipayParameter = new HashMap<String, String>();
        for (Iterator<String> iter = parameterMap.keySet().iterator(); iter.hasNext(); ) {
            String name = (String) iter.next();

            String[] values = parameterMap.get(name);
            String valueStr = "";
            for (int i = 0; i < values.length; i++) {
                valueStr = (i == values.length - 1) ? valueStr + values[i] : valueStr + values[i] + ",";
            }
            alipayParameter.put(name, valueStr);
        }

        return alipayParameter;
    }
}

Go代码

package main

import (
	"bytes"
	"crypto/hmac"
	"crypto/sha256"
	"encoding/base64"
	"fmt"
	"net/url"
	"sort"
	"time"
)

// Signature used to generate signature with the appsecret/method/params/RequestURI
func Signature(appsecret, method string, params url.Values, RequestURL string) (result string) {
	var b bytes.Buffer
	keys := make([]string, len(params))
	pa := make(map[string]string)
	for k, v := range params {
		pa[k] = v[0]
		keys = append(keys, k)
	}

	sort.Strings(keys)

	for _, key := range keys {
		if key == "_signature" {
			continue
		}

		val := pa[key]
		if key != "" && val != "" {
			b.WriteString(key)
			b.WriteString(val)
		}
	}

	stringToSign := fmt.Sprintf("%v\n%v\n%v\n", method, b.String(), RequestURL)

	
	fmt.Println(stringToSign)

	sha256 := sha256.New
	hash := hmac.New(sha256, []byte(appsecret))

	hash.Write([]byte(stringToSign))

	return base64.StdEncoding.EncodeToString(hash.Sum(nil))
}

func main() {
	//http://xxx:8091/v2api/dv/filter?_appid=test&_signature=sdfdf&_timestamp=2017-11-09%2019%3A39%3A00&_index=app_service_homework_miniactivefinish_di&_type=app_service_homework_miniactivefinish_di&_size=10&_from=0
	appsecret := "bigdatawmw"
	method := "GET"
	RequestURL := "/v2api/dv/filter"
	params := make(url.Values)
	params.Add("_appid", "bigdata")
	params.Add("_timestamp", time.Now().Format("2006-01-02 15:04:05"))
	params.Add("_index", "app_service_superbook_packorder_da")
	params.Add("_type", "app_service_superbook_packorder_da")
	params.Add("_size", "10")
	params.Add("_from", "0")

	fmt.Println("http://xxx:8091" + RequestURL + "?" + params.Encode() + "&_signature=" + Signature(appsecret, method, params, RequestURL))

	//signature := "mFdpvLh48ca4mDVEItE9++AKKQ/IVca7O/ZyyB8hR58="
	// fmt.Println(Signature(appsecret, method, params, RequestURL))

	fmt.Println(time.Now().Format("2006-01-02 15:04:05"))

}

转载于:https://my.oschina.net/u/1000241/blog/889711

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值