目前移动端流行? ,本文章主要介绍本人(新手) 开发 与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 中注册即可
/// <summary>
? ? /// 处理请求参数和响应消息的加解密
? ? /// </summary>
? ? public class MessageHandler : MessageProcessingHandler
? ? {
? ? ? ? #region Private Fields & Const
? ? ? ? private readonly TelemetryClient _client;
? ? ? ? #endregion
? ? ? ? #region Constructor
? ? ? ? /// <summary>
? ? ? ? /// Constructor
? ? ? ? /// </summary>
? ? ? ? public MessageHandler()
? ? ? ? {
? ? ? ? ? ? _client = new TelemetryClient();
? ? ? ? }
? ? ? ? #endregion
? ? ? ? #region Override Methods
? ? ? ? /// <summary>
? ? ? ? /// 处理request
? ? ? ? /// 1、消息解密
? ? ? ? /// </summary>
? ? ? ? /// <param name="request">Http 请求</param>
? ? ? ? /// <param name="cancellationToken">cancellation token</param>
? ? ? ? /// <returns>处理后的http request</returns>
? ? ? ? protected override HttpRequestMessage ProcessRequest(HttpRequestMessage request,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? CancellationToken cancellationToken)
? ? ? ? {
? ? ? ? ? ? // 过滤swagger请求
? ? ? ? ? ? if (request.RequestUri.Segments.Contains("swagger"))
? ? ? ? ? ? ? ? return request;
? ? ? ? ? ? try
? ? ? ? ? ? {
? ? ? ? ? ? ? ? var method = request.Method.Method.ToLowerInvariant();
? ? ? ? ? ? ? ? if ("get" == method || "delete" == method)
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? if (request.RequestUri.Query.Length <= 0)
? ? ? ? ? ? ? ? ? ? ? ? return request;
? ? ? ? ? ? ? ? ? ? var encryptedStr = request.RequestUri.Query?.Substring(1, request.RequestUri.Query.Length - 1);
? ? ? ? ? ? ? ? ? ? if (!string.IsNullOrEmpty(encryptedStr))
? ? ? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? ? ? var parameters = HandlerUtility.AES128Decrypt(encryptedStr);
? ? ? ? ? ? ? ? ? ? ? ? var url = $"{request.RequestUri.AbsoluteUri.Split('?')[0]}?{ parameters }";
? ? ? ? ? ? ? ? ? ? ? ? request.RequestUri = new Uri($"{request.RequestUri.AbsoluteUri.Split('?')[0]}?{ parameters }");
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? else
? ? ? ? ? ? ? ? {
/// <summary>
? ? /// Message handler to validate and decrypt request parameter
? ? /// </summary>
? ? public class AuthenticationHandler : DelegatingHandler
? ? {
? ? ? ? #region Private Fields
? ? ? ? private readonly TelemetryClient _client;
? ? ? ? #endregion
? ? ? ? #region Constructor
? ? ? ? /// <summary>
? ? ? ? /// Constructor
? ? ? ? /// </summary>
? ? ? ? public AuthenticationHandler()
? ? ? ? {
? ? ? ? ? ? _client = new TelemetryClient();
? ? ? ? }
? ? ? ? #endregion
? ? ? ? #region Override Methods
? ? ? ? /// <summary>
? ? ? ? /// 验证请求参数
? ? ? ? /// </summary>
? ? ? ? /// <param name="request">http请求</param>
? ? ? ? /// <param name="token">cancellation token</param>
? ? ? ? /// <returns>
? ? ? ? /// HttpResponseMessage
? ? ? ? /// </returns>
? ? ? ? protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? CancellationToken token)
? ? ? ? {
? ? ? ? ? ? try
? ? ? ? ? ? {
? ? ? ? ? ? ? ? // 检查时间戳
? ? ? ? ? ? ? ? if (!CheckTimestamp(request.Headers))
? ? ? ? ? ? ? ? ? ? return GenerateErrorResponse(request, true);
? ? ? ? ? ? ? ? // 检查签名
? ? ? ? ? ? ? ? var parameters = GetParameters(request);
? ? ? ? ? ? ? ? if (!CheckSignature(request.Headers, parameters))
? ? ? ? ? ? ? ? ? ? return GenerateErrorResponse(request, true);
? ? ? ? ? ? }
? ? ? ? ? ? catch (Exception ex)
? ? ? ? ? ? {
? ? ? ? ? ? ? ? return GenerateErrorResponse(request, true, "请求失败!");
? ? ? ? ? ? }
? ? ? ? ? ? return base.SendAsync(request, token);
? ? ? ? }
? ? ? ? #endregion
? ? ? ? #region Private Methods
? ? ? ? /// <summary>
? ? ? ? /// 获取请求参数
? ? ? ? /// </summary>
? ? ? ? /// <param name="request">http 请求</param>
? ? ? ? /// <returns>请求参数</returns>
? ? ? ? private static string GetParameters(HttpRequestMessage request)
? ? ? ? {
? ? ? ? ? ? if (request == null)
? ? ? ? ? ? ? ? return string.Empty;
? ? ? ? ? ? string parameters = null;
? ? ? ? ? ? if ("get".Equals(request.Method.Method, StringComparison.InvariantCultureIgnoreCase) ||
? ? ? ? ? ? ? ? "delete".Equals(request.Method.Method, StringComparison.InvariantCultureIgnoreCase))
? ? ? ? ? ? {
? ? ? ? ? ? ? ? if (!string.IsNullOrEmpty(request.RequestUri.OriginalString) &&
? ? ? ? ? ? ? ? ? request.RequestUri.OriginalString.Length > 1)
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? var urlArray = request.RequestUri.OriginalString.Split('?');
? ? ? ? ? ? ? ? ? ? if (urlArray != null && urlArray.Length > 1)
? ? ? ? ? ? ? ? ? ? ? ? parameters = urlArray[1];
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? ? ? else
? ? ? ? ? ? {
? ? ? ? ? ? ? ? // post/put/delete
? ? ? ? ? ? ? ? parameters = request.Content.ReadAsStringAsync().Result;
? ? ? ? ? ? }
? ? ? ? ? ? return parameters;
? ? ? ? }
? ? ? ? /// <summary>
? ? ? ? /// 返回客户端错误信息
? ? ? ? /// </summary>
? ? ? ? /// <param name="request">http请求</param>
? ? ? ? /// <param name="needEncrypt">是否加密响应信息</param>
? ? ? ? /// <param name="errorMessage">错误信息</param>
? ? ? ? /// <returns>
? ? ? ? /// 异步方式返回的错误消息
? ? ? ? /// </returns>
? ? ? ? private Task<HttpResponseMessage> 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<HttpResponseMessage>.Factory.StartNew(() => response);
? ? ? ? }
? ? ? ? /// <summary>
? ? ? ? /// 验证错误的请求记录日志
? ? ? ? /// </summary>
? ? ? ? /// <param name="request">http请求</param>
? ? ? ? private void LogErrorRequest(HttpRequestMessage request)
? ? ? ? {
? ? ? ? ? ? if (request == null)
? ? ? ? ? ? ? ? return;
? ? ? ? ? ? // 记录请求参数
? ? ? ? ? ? var method = request.Method.Method.ToLowerInvariant();
? ? ? ? ? ? if ("get" == method || "delete" == method)
? ? ? ? ? ? {
? ? ? ? ? ? ? ? //记录下相关日志? 以备查看
? ? ? ? ? ? }
? ? ? ? ? ? else
? ? ? ? ? ? {
? ? ? ? ? ? ? ? request.Content.ReadAsStreamAsync().Result.Seek(0, SeekOrigin.Begin);
? ? ? ? ? ? ? ? var body = request.Content.ReadAsStringAsync().Result;
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? /// <summary>
? ? ? ? /// 验证时间戳
? ? ? ? /// </summary>
? ? ? ? /// <param name="headers">请求头信息</param>
? ? ? ? /// <returns>
? ? ? ? /// true表示验证成功
? ? ? ? /// false表示验证失败
? ? ? ? /// </returns>
? ? ? ? private static bool CheckTimestamp(HttpRequestHeaders headers)
? ? ? ? {
? ? ? ? ? ? if (headers == null)
? ? ? ? ? ? ? ? return false;
? ? ? ? ? ? long timestamp = 0;
? ? ? ? ? ? if (headers.Contains("timestamp"))
? ? ? ? ? ? ? ? long.TryParse(headers.GetValues("timestamp").FirstOrDefault(), out timestamp);
? ? ? ? ? ? // 请求URL的有效期为15分钟,防止重放攻击
? ? ? ? ? ? return GetTotalMillisecondsSince1970() - timestamp < 15 * 60 * 1000;
? ? ? ? }
? ? ? ? /// <summary>
? ? ? ? /// 验证签名
? ? ? ? /// </summary>
? ? ? ? /// <param name="headers">请求头信息</param>
? ? ? ? /// <param name="parameters">
? ? ? ? /// 解密后的请求参数
? ? ? ? /// 对于get/delete请求,parameter为querystring
? ? ? ? /// 对于post/put/patch, paramters为json字符串
? ? ? ? /// </param>
? ? ? ? /// <returns>
? ? ? ? /// true 表示验证成功
? ? ? ? /// false表示验证失败
? ? ? ? /// </returns>
? ? ? ? private static bool CheckSignature(HttpRequestHeaders headers, string parameters)
? ? ? ? {
? ? ? ? ? ? if (headers == null ||
? ? ? ? ? ? ? !headers.Contains("sign") ||
? ? ? ? ? ? ? !headers.Contains("timestamp") ||
? ? ? ? ? ? ? !headers.Contains("random"))
? ? ? ? ? ? ? ? return false;
? ? ? ? ? ? // 客户端传过来的签名
? ? ? ? ? ? var sign = headers.GetValues("sign")?.FirstOrDefault();
? ? ? ? ? ? // 服务器重新计算签名
? ? ? ? ? ? string computedSign = null;
? ? ? ? ? ? var timestamp = headers.GetValues("timestamp")?.FirstOrDefault();
? ? ? ? ? ? var random = headers.GetValues("random")?.FirstOrDefault();
? ? ? ? ? ? computedSign = HandlerUtility.SHA256Encode(parameters + timestamp + random);
? ? ? ? ? ? // 签名不一致,参数被篡改
? ? ? ? ? ? var base64Sign = sign.Replace("$", "+").Replace("*", "=");
? ? ? ? ? ? return string.Equals(base64Sign, computedSign, StringComparison.InvariantCulture);
? ? ? ? }
? ? ? ? #endregion
? ? }
// 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 }