webapi服务端对接app

目前移动端流行? ,本文章主要介绍本人(新手) 开发 与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 }

?著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,100评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,308评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事?!?“怎么了?”我有些...
    开封第一讲书人阅读 159,718评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,275评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,376评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,454评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,464评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,248评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,686评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,974评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,150评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,817评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,484评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,140评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,374评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,012评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,041评论 2 351

推荐阅读更多精彩内容

  • pyspark.sql?????樯舷挛?Spark SQL和DataFrames的重要类: pyspark.sql...
    mpro阅读 9,449评论 0 13
  • 英文文档,一开始我也是抗拒的,边翻译边看,也就花费了1个小时基本就阅读过了,我的英文基础其实很差。附上链接:链接:...
    lonecolonel阅读 9,886评论 3 1
  • =========================================================...
    _灯火阑珊处阅读 2,411评论 0 3
  • 原文https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html...
    梁行之阅读 1,097评论 0 0
  • "use strict";function _classCallCheck(e,t){if(!(e instanc...
    久些阅读 2,028评论 0 2