WebApi系列(lie)~安(an)全校(xiao)驗中的(de)防篡改和防復用(yong)
web api越(yue)來(lai)越(yue)火,因為它(ta)的(de)(de)(de)跨平臺,因為它(ta)的(de)(de)(de)簡單(dan),因為它(ta)支持(chi)xml,json等(deng)流行的(de)(de)(de)數(shu)(shu)據協議,我們在(zai)(zai)開發基于面向服務的(de)(de)(de)API時,有(you)個(ge)(ge)(ge)問題一(yi)直在(zai)(zai)困擾著我們,那就(jiu)是數(shu)(shu)據的(de)(de)(de)安全(quan),請(qing)求的(de)(de)(de)安全(quan),一(yi)般所說的(de)(de)(de)安全(quan)也無(wu)非就(jiu)是請(qing)求的(de)(de)(de)防(fang)篡改和請(qing)求的(de)(de)(de)防(fang)復用,例(li)如(ru),你(ni)(ni)向API發一(yi)個(ge)(ge)(ge)查詢用戶(hu)賬戶(hu)的(de)(de)(de)請(qing)求,在(zai)(zai)這個(ge)(ge)(ge)過(guo)程中,你(ni)(ni)可(ke)能要(yao)傳(chuan)遞用戶(hu)ID,用戶(hu)所在(zai)(zai)項(xiang)目ID等(deng),而現(xian)在(zai)(zai)攔截工具如(ru)此盛行,很(hen)容易(yi)就(jiu)可(ke)以把它(ta)的(de)(de)(de)請(qing)求攔截,然(ran)后篡改,再轉發,這樣你(ni)(ni)的(de)(de)(de)API就(jiu)是不安全(quan)的(de)(de)(de),而對于訂(ding)單(dan),賬戶(hu)模塊(kuai)這種糟(zao)糕(gao)的(de)(de)(de)API設計(ji)更是致命的(de)(de)(de),可(ke)能引起的(de)(de)(de)損失是不可(ke)預計(ji)的(de)(de)(de),是災難(nan)性的(de)(de)(de),拍拍腦子想想就(jiu)知道(dao),你(ni)(ni)向項(xiang)目提現(xian)10元,我把請(qing)求攔截把10改成(cheng)100000,那么這個(ge)(ge)(ge)將(jiang)是一(yi)個(ge)(ge)(ge)災難(nan)!
防篡改
一(yi)(yi)般(ban)使用的(de)方式就(jiu)是把參數(shu)拼(pin)接,加(jia)上雙方約定的(de)“密鑰(yao)”,加(jia)上你的(de)當(dang)前(qian)項目AppKey,做一(yi)(yi)次MD5加(jia)密,這個過(guo)程生成的(de)字符串(chuan)我(wo)們(men)一(yi)(yi)般(ban)稱(cheng)為密文,而(er)對應的(de)可見(jian)參數(shu)我(wo)們(men)叫明文,其中明文和(he)(he)密文用于網絡(luo)傳輸,而(er)密鑰(yao)存儲在本地和(he)(he)服務器,不能(neng)進行傳輸!
防復用
一般請求,被重(zhong)復的(de)(de)使用,也是(shi)正常的(de)(de),就上面(mian)的(de)(de)方式(shi)進行加密(mi),就無法解決防復用的(de)(de)問(wen)題,這(zhe)(zhe)時(shi)我(wo)們需要在(zai)客戶端(duan)和服務端(duan)分別生成UTC的(de)(de)時(shi)間戳(chuo),這(zhe)(zhe)個UTC是(shi)防止(zhi)你的(de)(de)客戶端(duan)與服務端(duan)不在(zai)同(tong)一個時(shi)區,呵呵,然后把時(shi)間戳(chuo)timestamp拼在(zai)密(mi)文里(li)就可(ke)以了(le),至(zhi)于(yu)防復用的(de)(de)有效性,我(wo)們可(ke)以自定(ding)義,當然大叔(shu)定(ding)義的(de)(de)是(shi)秒,即同(tong)一秒內,請求可(ke)以重(zhong)復發送。
大叔API安全結構圖
web api核心安全校驗代碼片斷(duan)
代碼供(gong)大家(jia)參考(kao)和學習,正式的項目可(ke)以根據自己公司的需要去設(she)計
/// <summary> /// 功(gong)能(neng):api數(shu)據安全(quan)性驗證(zheng) /// 校驗方(fang)式(shi):ciphertext=md5(form鍵的(de)值拼接(jie)(jie)+timestamp+passkey),服務端用接(jie)(jie)收到(dao)的(de)表單(dan)數據與時間戳和自己的(de)passkey進行md5生成,最(zui)后比較值是否一(yi)致(zhi) /// passkey為私鑰,不用(yong)于(yu)網(wang)絡傳遞(di),你(ni)可以將它(ta)與appKey進行關聯,appKey用(yong)來傳遞(di),服務器(qi)根據appKey去(qu)數據庫里取(qu)對應的passkey然后進行比較 /// 功(gong)能:請求唯一性,防偽造性 /// timestamp:UTC時(shi)間(jian)(jian)戳,不用于(yu)網(wang)絡傳遞,在(zai)客戶(hu)端調用服(fu)務器(qi)(qi)時(shi),服(fu)務器(qi)(qi)也生成yyyyMMddhhmmss的時(shi)間(jian)(jian)戳,然后進行計(ji)算,看是否(fou)過期(qi) /// </summary> [AttributeUsage(AttributeTargets.Method)] public class ApiValidateFilter : ActionFilterAttribute { public override void OnActionExecuting(System.Web.Http.Controllers.HttpActionContext actionContext) { #region 初始化 var context = (HttpContextBase)actionContext.Request.Properties["MS_HttpContext"];//獲取傳統context var request = context.Request;//定義傳統request對象 var paramStr = new StringBuilder(); var coll = new NameValueCollection(); if (request.HttpMethod.ToLower() == "get") coll = request.QueryString; else coll = request.Form; #endregion #region 解析XML配置文件 var config = CacheConfigFile.ConfigFactory.Instance.GetConfig<ApiValidateModelConfig>().ApiValidateModelList.FirstOrDefault(i => i.AppKey == coll["AppKey"]); if (config == null) { actionContext.Response = new HttpResponseMessage(HttpStatusCode.Forbidden) { Content = new StringContent("AppKey不是合并的,請先去組(zu)織生成有(you)效的Key", Encoding.GetEncoding("UTF-8")) }; base.OnActionExecuting(actionContext); } if (config.ExpireDate < DateTime.Now) { actionContext.Response = new HttpResponseMessage(HttpStatusCode.Forbidden) { Content = new StringContent("AppKey不(bu)是合并的,密鑰已過期", Encoding.GetEncoding("UTF-8")) }; base.OnActionExecuting(actionContext); } #endregion #region 驗證算法 var keys = new List<string>(); foreach (string param in coll.Keys) { if (!string.IsNullOrEmpty(param)) { keys.Add(param.ToLower()); } } keys.Sort(); foreach (string p in keys) { if (p != "ciphertext") { if (!string.IsNullOrEmpty(coll[p])) { paramStr.Append(coll[p]); } } } paramStr.Append(DateTime.Now.ToUniversalTime().ToString("yyyyMMddHHmmss")); paramStr.Append(config.PassKey); #endregion if (Lind.DDD.Utils.Encryptor.Utility.EncryptString(paramStr.ToString(), Lind.DDD.Utils.Encryptor.Utility.EncryptorType.MD5) != request["cipherText"]) { actionContext.Response = new HttpResponseMessage(HttpStatusCode.Forbidden) { Content = new StringContent("驗證失敗,請求非(fei)法", Encoding.GetEncoding("UTF-8")) }; } base.OnActionExecuting(actionContext); } }
在(zai)上(shang)的配置(zhi)項大叔(shu)把它存(cun)(cun)(cun)(cun)儲到的XML里,使(shi)用的是大叔(shu)自己封(feng)裝的XML緩(huan)存(cun)(cun)(cun)(cun)組件(jian)CacheConfigFile,文件(jian)第(di)一(yi)次(ci)訪問會加(jia)載(zai)到內存(cun)(cun)(cun)(cun),下次(ci)使(shi)用直接從內存(cun)(cun)(cun)(cun)返(fan)回,而當文件(jian)修改后,文件(jian)的最后更新(xin)時間發生(sheng)變化,這時緩(huan)存(cun)(cun)(cun)(cun)過期,在(zai)生(sheng)產緩(huan)存(cun)(cun)(cun)(cun)時,還(huan)是采用了單例(li)模式,
這個在大叔(shu)框(kuang)架里經(jing)常(chang)被看到,呵呵。