查看: 492|回复: 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 }
复制代码

  



回复

使用道具 举报