互聯網通用架構技術----公網API安全規範

基於公網的http協議的請求/響應時不安全的,存在被截獲,篡改,重發的可能。算法

Restful API接口要求

  • 防假裝攻擊(如:公網,第三方有意或惡意的調用接口);api

  • 防篡改攻擊(如:公網,請求頭/查詢字符串/內容,在傳輸過程當中被篡改);數組

  • 防重發攻擊(如:公網,請求被截獲,稍後被重發屢次);安全

  • 防數據信息泄露(如:截獲用戶請求,截獲到帳號,密碼等);服務器

基於HTTP協議的接口(通用數據交互接口)須要知足前三條要求(主要經過時間戳和簽名),第四條採用HTTPS協議或傳輸內容使用非對稱加密(用在用戶登陸,受權,解密接口)。app

基於內網的API

經過簽名,IP白名單搞定。工具

參考HTTPS方案

參考OAuth受權機制

基於HTTP方案

設計原則:

  • 輕量級;測試

  • 適合於異構系統(跨操做系統,多語言簡易實現);ui

  • 易於開發;編碼

  • 易於測試;

  • 易於部署;

  • 知足接口安全要求,無過分設計;

接口簽名參考:

  • _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"))

}
相關文章
相關標籤/搜索