查看: 3580|回復: 0

[.NET開發] webapi服務端對接app

發表于 2018-4-30 20:33:04

目前移動端流行 ,本文章主要介紹本人(新手) 開發 與app對接服務端 進行分享 。不足之處請指正

與app對接 一般的站點接口 需映射外網(即外部網絡可以直接訪問該接口項目),那么 這就要考慮到項目的數據保密性和一些驗證。

目前 我做的接口中所用到了 如下技術:

1:數據的加密/解密: 數據在傳輸過程中 需要進行加解密操作 才能有效的保護數據的安全性 (我的項目中采用.net framwork 自帶的aes 加解密)

數據加密 可采用自定的加密方式(如 AES DES )等 這里就不貼代碼了。

2:簽名驗證:即 app端將數據進行 不可逆加密 然后 放在http請求的header中 ,服務端 獲取到 數據 采用相同的不可逆加密方式進行獲得 密文 ,將app的密文和服務端解析的密文進行匹配 (可在一定程度上保護數據不被篡改)

3:時間戳驗證:app端 在http請求中 加入時間戳 數據 ,服務端獲取時間戳 ,去驗證 app的時間戳 與服務器端 設定的時間戳 ,防止重放攻擊

4:登錄時效驗證:在每次登錄時 產生一個唯一的token驗證碼 存儲在數據庫中 ,并將該 token返回至APP ,app每次請求數據 去驗證 token是否 已過時,如果過時 重新登錄

在 項目開發過程中 對于以上的驗證 建議統一處理

1:在請求數據進入時 統一進行 參數的解密和驗證

  方法:統一解密 webapi 項目 可以繼承類 MessageProcessingHandler 并重寫 ProcessRequest 和 ProcessResponse 方法

  在該ProcessRequest 方法中進行數據的解密

  在該ProcessResponse 方法中進行數據的加密

2.數據驗證 繼承 類 DelegatingHandler ,并重寫 SendAsync 方法 進行 數據的相關驗證, 一般 如果驗證 通過 結果直接 return base.SendAsync(request, token); 即 最終的效果為 將消息分發到 對應的接口中 進行處理

如果驗證不通過 可 直接采用如下方式直接 返回信息

///


/// 返回客戶端錯誤信息
///

/// http請求
/// 是否加密響應信息
/// 錯誤信息
///
/// 異步方式返回的錯誤消息
///

private Task GenerateErrorResponse(HttpRequestMessage request,
bool needEncrypt,
string errorMessage = "請求參數錯誤")
{
// 記錄錯誤的請求日志
LogErrorRequest(request);

// 生成錯誤響應消息
var response = new HttpResponseMessage();
var error = JsonConvert.SerializeObject(new ApiResult() { Message = errorMessage });
response.Content = new StringContent(error, Encoding.GetEncoding("UTF-8"), "application/json");
response.StatusCode = System.Net.HttpStatusCode.OK;
if(needEncrypt)
response.Content.Headers.Add("toencrypt", "");

return Task.Factory.StartNew(() => response);
}

下面就到 代碼 showTime: 以下 兩個類創建之后 在 global 中注冊即可

  1. /// <summary>
  2. /// 處理請求參數和響應消息的加解密
  3. /// </summary>
  4. public class MessageHandler : MessageProcessingHandler
  5. {
  6. #region Private Fields & Const
  7. private readonly TelemetryClient _client;
  8. #endregion
  9. #region Constructor
  10. /// <summary>
  11. /// Constructor
  12. /// </summary>
  13. public MessageHandler()
  14. {
  15. _client = new TelemetryClient();
  16. }
  17. #endregion
  18. #region Override Methods
  19. /// <summary>
  20. /// 處理request
  21. /// 1、消息解密
  22. /// </summary>
  23. /// <param name="request">Http 請求</param>
  24. /// <param name="cancellationToken">cancellation token</param>
  25. /// <returns>處理后的http request</returns>
  26. protected override HttpRequestMessage ProcessRequest(HttpRequestMessage request,
  27. CancellationToken cancellationToken)
  28. {
  29. // 過濾swagger請求
  30. if (request.RequestUri.Segments.Contains("swagger"))
  31. return request;
  32. try
  33. {
  34. var method = request.Method.Method.ToLowerInvariant();
  35. if ("get" == method || "delete" == method)
  36. {
  37. if (request.RequestUri.Query.Length <= 0)
  38. return request;
  39. var encryptedStr = request.RequestUri.Query?.Substring(1, request.RequestUri.Query.Length - 1);
  40. if (!string.IsNullOrEmpty(encryptedStr))
  41. {
  42. var parameters = HandlerUtility.AES128Decrypt(encryptedStr);
  43. var url = $"{request.RequestUri.AbsoluteUri.Split('?')[0]}?{ parameters }";
  44. request.RequestUri = new Uri($"{request.RequestUri.AbsoluteUri.Split('?')[0]}?{ parameters }");
  45. }
  46. }
  47. else
  48. {
復制代碼
  1. /// <summary>
  2. /// Message handler to validate and decrypt request parameter
  3. /// </summary>
  4. public class AuthenticationHandler : DelegatingHandler
  5. {
  6. #region Private Fields
  7. private readonly TelemetryClient _client;
  8. #endregion
  9. #region Constructor
  10. /// <summary>
  11. /// Constructor
  12. /// </summary>
  13. public AuthenticationHandler()
  14. {
  15. _client = new TelemetryClient();
  16. }
  17. #endregion
  18. #region Override Methods
  19. /// <summary>
  20. /// 驗證請求參數
  21. /// </summary>
  22. /// <param name="request">http請求</param>
  23. /// <param name="token">cancellation token</param>
  24. /// <returns>
  25. /// HttpResponseMessage
  26. /// </returns>
  27. protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
  28. CancellationToken token)
  29. {
  30. try
  31. {
  32. // 檢查時間戳
  33. if (!CheckTimestamp(request.Headers))
  34. return GenerateErrorResponse(request, true);
  35. // 檢查簽名
  36. var parameters = GetParameters(request);
  37. if (!CheckSignature(request.Headers, parameters))
  38. return GenerateErrorResponse(request, true);
  39. }
  40. catch (Exception ex)
  41. {
  42. return GenerateErrorResponse(request, true, "請求失敗!");
  43. }
  44. return base.SendAsync(request, token);
  45. }
  46. #endregion
  47. #region Private Methods
  48. /// <summary>
  49. /// 獲取請求參數
  50. /// </summary>
  51. /// <param name="request">http 請求</param>
  52. /// <returns>請求參數</returns>
  53. private static string GetParameters(HttpRequestMessage request)
  54. {
  55. if (request == null)
  56. return string.Empty;
  57. string parameters = null;
  58. if ("get".Equals(request.Method.Method, StringComparison.InvariantCultureIgnoreCase) ||
  59. "delete".Equals(request.Method.Method, StringComparison.InvariantCultureIgnoreCase))
  60. {
  61. if (!string.IsNullOrEmpty(request.RequestUri.OriginalString) &&
  62. request.RequestUri.OriginalString.Length > 1)
  63. {
  64. var urlArray = request.RequestUri.OriginalString.Split('?');
  65. if (urlArray != null && urlArray.Length > 1)
  66. parameters = urlArray[1];
  67. }
  68. }
  69. else
  70. {
  71. // post/put/delete
  72. parameters = request.Content.ReadAsStringAsync().Result;
  73. }
  74. return parameters;
  75. }
  76. /// <summary>
  77. /// 返回客戶端錯誤信息
  78. /// </summary>
  79. /// <param name="request">http請求</param>
  80. /// <param name="needEncrypt">是否加密響應信息</param>
  81. /// <param name="errorMessage">錯誤信息</param>
  82. /// <returns>
  83. /// 異步方式返回的錯誤消息
  84. /// </returns>
  85. private Task<HttpResponseMessage> GenerateErrorResponse(HttpRequestMessage request,
  86. bool needEncrypt,
  87. string errorMessage = "請求參數錯誤")
  88. {
  89. // 記錄錯誤的請求日志
  90. LogErrorRequest(request);
  91. // 生成錯誤響應消息
  92. var response = new HttpResponseMessage();
  93. var error = JsonConvert.SerializeObject(new ApiResult() { Message = errorMessage });
  94. response.Content = new StringContent(error, Encoding.GetEncoding("UTF-8"), "application/json");
  95. response.StatusCode = System.Net.HttpStatusCode.OK;
  96. if(needEncrypt)
  97. response.Content.Headers.Add("toencrypt", "");
  98. return Task<HttpResponseMessage>.Factory.StartNew(() => response);
  99. }
  100. /// <summary>
  101. /// 驗證錯誤的請求記錄日志
  102. /// </summary>
  103. /// <param name="request">http請求</param>
  104. private void LogErrorRequest(HttpRequestMessage request)
  105. {
  106. if (request == null)
  107. return;
  108. // 記錄請求參數
  109. var method = request.Method.Method.ToLowerInvariant();
  110. if ("get" == method || "delete" == method)
  111. {
  112. //記錄下相關日志 以備查看
  113. }
  114. else
  115. {
  116. request.Content.ReadAsStreamAsync().Result.Seek(0, SeekOrigin.Begin);
  117. var body = request.Content.ReadAsStringAsync().Result;
  118. }
  119. }
  120. /// <summary>
  121. /// 驗證時間戳
  122. /// </summary>
  123. /// <param name="headers">請求頭信息</param>
  124. /// <returns>
  125. /// true表示驗證成功
  126. /// false表示驗證失敗
  127. /// </returns>
  128. private static bool CheckTimestamp(HttpRequestHeaders headers)
  129. {
  130. if (headers == null)
  131. return false;
  132. long timestamp = 0;
  133. if (headers.Contains("timestamp"))
  134. long.TryParse(headers.GetValues("timestamp").FirstOrDefault(), out timestamp);
  135. // 請求URL的有效期為15分鐘,防止重放攻擊
  136. return GetTotalMillisecondsSince1970() - timestamp < 15 * 60 * 1000;
  137. }
  138. /// <summary>
  139. /// 驗證簽名
  140. /// </summary>
  141. /// <param name="headers">請求頭信息</param>
  142. /// <param name="parameters">
  143. /// 解密后的請求參數
  144. /// 對于get/delete請求,parameter為querystring
  145. /// 對于post/put/patch, paramters為json字符串
  146. /// </param>
  147. /// <returns>
  148. /// true 表示驗證成功
  149. /// false表示驗證失敗
  150. /// </returns>
  151. private static bool CheckSignature(HttpRequestHeaders headers, string parameters)
  152. {
  153. if (headers == null ||
  154. !headers.Contains("sign") ||
  155. !headers.Contains("timestamp") ||
  156. !headers.Contains("random"))
  157. return false;
  158. // 客戶端傳過來的簽名
  159. var sign = headers.GetValues("sign")?.FirstOrDefault();
  160. // 服務器重新計算簽名
  161. string computedSign = null;
  162. var timestamp = headers.GetValues("timestamp")?.FirstOrDefault();
  163. var random = headers.GetValues("random")?.FirstOrDefault();
  164. computedSign = HandlerUtility.SHA256Encode(parameters + timestamp + random);
  165. // 簽名不一致,參數被篡改
  166. var base64Sign = sign.Replace("$", "+").Replace("*", "=");
  167. return string.Equals(base64Sign, computedSign, StringComparison.InvariantCulture);
  168. }
  169. #endregion
  170. }
復制代碼

  

  1. // post/put/patch var encryptedStr = request.Content.ReadAsStringAsync().Result; if (!string.IsNullOrEmpty(encryptedStr)) { var parameters = HandlerUtility.AES128Decrypt(encryptedStr);//加解密自定義方法 request.Content = new StringContent(parameters); request.Content.Headers.ContentLength = parameters.Length; request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); } } } catch (Exception ex) { _client.TrackException(ex); } return request; } /// <summary> /// 處理response /// 1、消息加密 /// </summary> /// <param name="response">明文響應消息</param> /// <param name="cancellationToken">cancelation token</param> /// <returns>密文響應消息</returns> protected override HttpResponseMessage ProcessResponse(HttpResponseMessage response, CancellationToken cancellationToken) { if (response.Content != null) { var contentType = response.Content.Headers.GetValues("content-type").FirstOrDefault(); if (response.Content.Headers.Contains("toencrypt")) { var plaintext = response.Content.ReadAsStringAsync().Result; response.Content = new StringContent(HandlerUtility.AES128Encrypt(plaintext)); response.Content.Headers.Remove("toencrypt"); } } return response; } #endregion }
復制代碼

  



回復

使用道具 舉報