標籤:
HTTP使用BASIC認證,WebAPI使用[HTTPBasicAuthorize]標記控制器就是使用了BASIC認證。 BASIC認證的缺點HTTP基本認證的目標是提供簡單的使用者驗證功能,其認證過程簡單明了,適合於對安全性要求不高的 系統或裝置中,如大家所用路由器的配置頁面的認證,幾乎 都採取了這種方式。其缺點是沒有靈活可靠的認證策略,如 無法提供域(domain或realm)認證功能,另外,BASE64的加密強度非常低,可以說僅 能防止sohu的搜尋把它搜到了。 當然,HTTP基本認證系統也可以與SSL或者Kerberos結合,實現安全效能較高(相對)的認證系統
難得的吐槽
逃回二線成都呆了兩年一直在做休閑娛樂行業的傳統管理軟體,由於該公司老闆太過於獨裁,反正股份分紅無望,幹得不爽。於是乎徹底逃出老家的縣城了。 半年來一直做了份遠端工作,非常感恩現在的BOSS,源了我在家辦公的心愿,雖然收入下降。 但是好在生活成本降低了許多,就是還個房貸和基本生活開銷、兩個寶貝上學的開銷。 還有就是接了一些私活,最近終於有空打算實現自己的產品夢了。我要做一套通用銷售計費軟體,原型已經做得七七八八了,就是架構上是單商戶的,不是多租戶的。 現在計劃是分布式應用架構,分傳統型程式端,安卓端,商戶平台端。由於前段時間客戶的用webapi和socket服務端做的中介軟體在ECS雲主機上不斷被攻擊,有一次居然把中介軟體程式搞死了。 所以這次使用WebAPI需要考慮使用安全防護機制了。 由於我這是個人的項目,太高深的安全防護肯定也是有門檻的。借鑒了銀聯POS協議,於是開始了這次的實踐。
主要驗證流程設計
1.用戶端 AuthenticationHeaderValue 請求的頭部
用戶端請求,規劃為 app_id:token,如下面例子就是在伺服器端使用"Request.Headers.Authorization.Parameter" 來擷取這個值,當然不能是明文吧,就是簡單的用Base64處理了下。
using (var httpClient = new HttpClient()) { var credentials = Convert.ToBase64String(Encoding.ASCII.GetBytes(string.Format("{0}:{1}", "datacool_winform", "27C68F9A899842A598DDBACD2806FDD7"))); httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", credentials); string url = "http://" + MiddlewareIP + ":5990" + "/api/CloudPOS/GetVersion?k=" + Guid.NewGuid().ToString(); try { string requestResult = httpClient.GetStringAsync(url).Result; return requestResult; } catch (Exception ex) { Com.DataCool.DotNetExpand.LogHelper.Error(ex); return string.Empty; } }
2.後台分需要授權驗證和不需要授權驗證的2個控制器
比如申請軟體試用,提交商戶門店資訊等就是不需要認證就可以發起請求,所以需要2個控制器
3.後台在資料庫裡控制和校正要求標頭部的app_id,token
後台就是寫一個類實現AuthorizeAttribute,即【HTTPBasicAuthorize】標示的攔截,代碼如下:
public class HTTPBasicAuthorizeAttribute : AuthorizeAttribute { /// <summary> /// 校正Authorization /// </summary> /// <param name="actionContext"></param> public override void OnAuthorization(HttpActionContext actionContext) { if (actionContext.Request.Headers.Authorization != null) { string[] agent_info = Encoding.Default.GetString(Convert.FromBase64String(actionContext.Request.Headers.Authorization.Parameter)).Split(":".ToArray()); //沒有按照預設的規則也是視為無權 if (agent_info.Length != 2) { HandleUnauthorizedRequest(actionContext); return; } string request_agent = agent_info.FirstOrDefault(); string token = agent_info.LastOrDefault(); #region 資料庫校正app_id和token using (var db = new POS_DB()) { try { db.Database.CreateIfNotExists(); } catch { } #region 預設授權 if (!db.sys_api_authorize.Any()) { var dt = DateTime.Now; var sys_scheme = new sys_api_authorize { merchant_name = "DataCool", request_scheme = "afeng124", request_token = "15730052377", master_key = Guid.NewGuid().ToString().Replace("-", ""), create_dt = dt, last_request_dt = dt, status = 1 }; db.sys_api_authorize.Add(sys_scheme); db.Entry<sys_api_authorize>(sys_scheme).State = System.Data.Entity.EntityState.Added; db.SaveChanges(); } #endregion var scheme_entity = db.sys_api_authorize .Where(s => s.request_scheme == request_agent && s.request_token == token && s.status == 1) .FirstOrDefault(); if (scheme_entity != null) { scheme_entity.last_request_dt = DateTime.Now; db.SaveChanges(); IsAuthorized(actionContext); } else { HandleUnauthorizedRequest(actionContext); } } #endregion } else { HandleUnauthorizedRequest(actionContext); } } /// <summary> /// 未通過認證,日誌進行記錄(發起請求的IP,請求的方法) /// </summary> /// <param name="actionContext"></param> protected override void HandleUnauthorizedRequest(HttpActionContext actionContext) { var challengeMessage = new HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized); challengeMessage.Headers.Add("WWW-Authenticate", "Basic"); if (actionContext.Request.Headers.Authorization == null) { string ip = actionContext.Request.GetClientIpAddress(); var request_url = actionContext.Request.RequestUri.AbsoluteUri.ToString(); var request_obj = new { RequestIP = ip, Request_Action = request_url, ErrorDesc = challengeMessage.StatusCode.ToString(), RequestMethod = actionContext.Request.Method.ToString(), Controller = actionContext.ControllerContext.ControllerDescriptor.ControllerName, RequestUrl = actionContext.Request.RequestUri.AbsoluteUri.ToString() }; Com.DataCool.DotNetExpand.LogHelper.Error(request_obj); } base.HandleUnauthorizedRequest(actionContext); //throw new HttpResponseException(challengeMessage); } }
上面如果直接throw會導致宿主服務程式異常,沒想到的是如果直接交給父類處理就行了。
沒通過認證那麼用戶端調用會出異常:
2016-10-09 17:02:39,916 層級:ERROR 日誌描述:System.AggregateException: 發生一個或多個錯誤。 ---> System.Net.Http.HttpRequestException: 響應狀態碼不指示成功: 401 (Unauthorized)。 --- 內部異常堆疊追蹤的結尾 --- 在 System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions) 在 System.Threading.Tasks.Task`1.GetResultCore(Boolean waitCompletionNotification) 在 System.Threading.Tasks.Task`1.get_Result() 在 MiddlewareService.MiddlewareServiceSvr.HttpAPIRequest() 位置 D:\cloudservice\WebAPIService\MiddlewareServiceSvr.cs:行號 97---> (內部異常 #0) System.Net.Http.HttpRequestException: 響應狀態碼不指示成功: 401 (Unauthorized)。
瀏覽器類比get的樣子是這樣:
4.改造軟體的推廣方式和BASIC認證結合起來
這個思路主要是這樣的:
1.商戶在官網上下載用戶端軟體前先申請試用,提交商戶的基本資料,這是營銷和售前服務的基礎。
2.商戶提交試用申請後下載傳統型程式,傳統型程式啟用一下調用api擷取主要金鑰和app_id和token。
3.登入商戶後台設定門店基礎參數。
4.伺服器可以控制使用到期日和功能
活學活用,webapi HTTPBasicAuthorize搭建小型雲應用的實踐