WebApi安全性 參數簽名校驗(結合Axios使用)

接口參數簽名校驗,是WebApi接口服務最重要的安全防禦手段之一. 結合項目中實際使用狀況,介紹下先後端參數簽名校驗實現方案。javascript

簽名校驗規則

http請求,有兩種傳參形式:html

1.經過url傳參,最多見的就是get請求(實際上post,put,delete均可以使用這種傳參方式),如:前端

http://api.XXX.com/getproduct?id=value1vue

2.經過request body傳參,最多見的就是post請求,以下圖所示
webapi參數簽名-01
咱們針對於以上兩種傳參方式,採用不一樣的簽名校驗規則(注:簽名算法規則僅供參考)。WebApi是不支持經過url和body同時傳參數的,因此在服務端能夠經過HttpContext.Current.Request.QueryString 獲取到form參數進行判斷,執行不一樣邏輯,以下代碼所示:java

var form = HttpContext.Current.Request.QueryString; // 請求的url參數
var data = string.Empty;
if (form.Count > 0)
{
    //第一步:取出全部form參數
    IDictionary<string, string> parameters = new Dictionary<string, string>();
    for (var f = 0; f < form.Count; f++)
    {
        var key = form.Keys[f];
    parameters.Add(key, form[key]);
    }

    // 第二步:把字典按Key的字母順序排序
    IDictionary<string, string> sortedParams = new SortedDictionary<string, string>(parameters);
    var dem = sortedParams.GetEnumerator();

    // 第三步:把全部參數名和參數值串在一塊兒
    var query = new StringBuilder();
    while (dem.MoveNext())
    {
        var key = dem.Current.Key;
        var value = dem.Current.Value;
        if (!string.IsNullOrEmpty(key)) query.Append(key).Append(value);
    }
    data = query.ToString();
}
else
{
    //請求輸入的內容,即body內容
    var stream = HttpContext.Current.Request.InputStream;
    stream.Position = 0;
    var responseJson = string.Empty;
    var streamReader = new StreamReader(stream);
    data = streamReader.ReadToEnd();
    stream.Position = 0;
}

經過上述邏輯以後,data變量中存儲的就是接口參數內容。 如下是實際簽名校驗邏輯ios

/// <summary>
/// 簽名校驗
/// </summary>
/// <param name="timeStamp">時間戳(按秒)</param>
/// <param name="data">參數內容</param>
/// <param name="signature">前端簽名值</param>
/// <returns></returns>
public bool Validate(string timeStamp, string data, string signature)
{
    var hash = MD5.Create();
    //拼接簽名數據
    var signStr = timeStamp + data;
    //將字符串中字符按升序排序
    var sortStr = string.Concat(signStr.OrderBy(c => c));
    var bytes = Encoding.UTF8.GetBytes(sortStr);
    //使用32位大寫 MD5簽名 
    var md5Val = hash.ComputeHash(bytes);
    var result = new StringBuilder();
    foreach (var c in md5Val) result.Append(c.ToString("X2"));
    var s = result.ToString().ToUpper();
    //與前端傳過來的簽名參數進行比對
    return s == signature;
}

Action攔截器實現對某些Api進行簽名校驗

建立WebApi的Action攔截器HandlerSecretAttributegit

/// <summary>
/// 簽名安全攔截過濾器
/// </summary>
public class HandlerSecretAttribute : ActionFilterAttribute
{
    private readonly ExcuteMode _customMode;

    /// <summary>默認構造</summary>
    /// <param name="Mode">認證模式</param>
    public HandlerSecretAttribute(ExcuteMode Mode)
    {
        _customMode = Mode;
    }

    /// <summary>
    /// 安全校驗
    /// </summary>
    /// <param name="filterContext"></param>
    public override void OnActionExecuting(HttpActionContext filterContext)
    {
        //是否忽略權限驗證
        if (_customMode == ExcuteMode.Ignore) return;

        //從http請求的頭裏面獲取AppId
        var request = filterContext.Request;
        var method = request.Method.Method;
        var appId = ""; //客戶端應用惟一標識
        long timeStamp; //時間戳, 校驗10分鐘內有效
        var signature = ""; //參數簽名,去除空參數,按字母倒序排序進行Md5簽名 爲了提升傳參過程當中,防止參數被惡意修改,在請求接口的時候加上sign能夠有效防止參數被篡改
        try
        {
            appId = request.Headers.GetValues("appId").SingleOrDefault();
            timeStamp = Convert.ToInt64(request.Headers.GetValues("timeStamp").SingleOrDefault());
            signature = request.Headers.GetValues("signature").SingleOrDefault();
        }
        catch (Exception ex)
        {
            throw new UserFriendlyException("簽名參數異常:" + ex.Message);
        }

        #region 安全校驗
        //TODO:實際邏輯處理
        base.OnActionExecuting(filterContext);
        #endregion
    }
}

具體的使用,以下圖所示:github

webapi參數簽名-02

若是是須要全局註冊,請在WebApi.config中配置,以下圖所示:web

webapi參數簽名-03

有關HandlerSecretAttribute的源代碼,我已整理放至github上https://github.com/yinboxie/BlogExampleDemoajax

前端Axios請求統一攔截處理

客戶端http請求(以Axios爲例)進行統一的攔截處理。前端用過諸多http插件,如ajax,fetch,vue-resoure,axios等,我的感受axios的請求攔截是最好用的。

import axios from 'axios'
import { sign } from './sign'
let _ = require('lodash')
var service = axios.create({
  baseURL:'http://xxx.com'
  timeout:10000 // 請求超時時間
})

// request攔截器
service.interceptors.request.use(
  config => {
    let token = getToken()
    if (token) {
      config.headers['token'] = token // 讓每一個請求攜帶自定義token 請根據實際狀況自行修改
    }
    // 若是接口須要簽名, 則經過請求時,headers中傳遞sign參數true
    if (config.headers['sign']) {
      config = sign(config) // 核心簽名邏輯,獨立封裝了處理函數
    }
    return config
  },
  error => {
    // Do something with request error
    console.log(error) // for debug
    Promise.reject(error)
  }
)

sign函數核心源碼

import md5 from 'js-md5'
let _ = require('lodash')
/**
 * 接口參數簽名
 * @param {*} config 請求配置
 */
export const sign = config => {
  // 獲取到秒級的時間戳,與後端對應
  let tmp = new Date()
    .getTime()
    .toString()
    .substr(0, 10)
  let header = {
    appId:'pmes',
    timeStamp: tmp,
    signature: ''
  }
  let signStr = _.toString(header.timeStamp)
  if (config.params) {
    // url參數簽名
    let pArray = []
    for (let p in config.params) {
      pArray.push(p)
    }
    let sArray = pArray.sort()
    for (let item of sArray) {
      signStr += item + _.toString(config.params[item])
    }
  } else if (config.data) {
    // request body參數的內容
    signStr += JSON.stringify(config.data)
  }

  // 簽名核心邏輯
  let newsignStr = _.sortBy(signStr, s => s.charCodeAt(0)).join('')
  let s = md5(newsignStr).toUpperCase()
  header.signature = s
  config = Object.assign(config, { headers: header })
  return config
}

實際的調用函數

// post請求, body傳參
axios.post('/Login/CheckLoginTest1',
    { Account: 'xiaowang',Password: '123' },
    { headers: { sign: true }}
).then(d => {
   console.log(d)
})
// post請求,url傳參
axios.post('/Login/CheckLoginTest3',
    null,
    {
      params: {t1: '2',t2: '3'},
      headers: { sign: true }
    }
).then(d => {
console.log(d)
})
// get請求
axios.get('/Login/CheckLoginTest2',
    {
      params: {t1: '2',t2: '3'},
      headers: { sign: true }
    }
).then(d => {
console.log(d)
})

總結

爲了保證WebApi數據在通訊時的安全性,須要採起多重安全防禦: 參數簽名校驗,token驗證,跨域權限,時間戳過時校驗,容許請求的appId校驗等。固然具體的規則咱們都得依據項目實際狀況去實現,這裏就再也不展開討論其餘方式的實現。

參考

WebApi安全性 使用TOKEN+簽名驗證

相關文章
相關標籤/搜索