最近公司有几个项目需要开发手机客户端,服务器端选用WebApi,那么如何保证手机客户端在请求服务器端时数据不被篡改,如何保证一个http请求的失效机制,下面总结一下我们在项目中针对这两个问题的解决方案。

基本思路如下:

  用户在成功登陆app客户端之后,手机客户端向服务器端发出的所有的http请求在请求头(HttpHeader)上都会带上下面三个参数:1、Uid(用户ID),2、Ts(时间戳),3、Sign(签名)。其中Ts是当前时间减去1970-1-1得到的10位的时间时间戳数字,Sign是接口中所有http请求参数与Uid、Ts经过MD5加密后得到的一个字符串。

具体实现如下(客户端的实现,手机客户端生成下面两个参数的思路是一样的):

1、Ts时间戳

Ts参数可以保证请求的时效性,在手机客户端生成的Ts,在服务器端验证一下,保证请求是在我们规定的时间段内,具体代码如下:

(1)、生成Ts(C#)代码如下,Andriod和IOS可以同理生成

 /// <summary>
/// 获取十位的时间戳
/// </summary>
/// <param name="dt"></param>
/// <returns></returns>
public string GenerateTimeStamp(DateTime dt)
{
// Default implementation of UNIX time of the current UTC time
TimeSpan ts = dt.ToUniversalTime() - new DateTime(, , , , , , );
return Convert.ToInt64(ts.TotalSeconds).ToString();
}

(2)、服务器端端验证Ts代码如下,我们规定从手机客户端发到服务器端的请求有效期为5分钟,时间戳参数是跟在Http请求头中

//获取请求头信息
var requestHeader = HttpContext.Current.Request.Headers; //10位时间戳
var Ts = requestHeader.Get("Ts");
//验证Ts是否合法(请求时间有效时间为:加减5分钟)
var ts = Ts;//10位时间戳
if (ts.Length != )
{
var resp = Request.CreateResponse<string>(HttpStatusCode.OK, "请求已过期", "application/json"); ; throw new HttpResponseException(resp);
}
var tsDate = ComHelper.ConvertIntDateTime(ts.ToString());
if (tsDate > DateTime.Now.AddMinutes() || tsDate < DateTime.Now.AddMinutes(-))
{
var resp = Request.CreateResponse<string>(HttpStatusCode.OK, "请求已过期", "application/json"); ; throw new HttpResponseException(resp);
}

(3)、ComHelper公共类代码如下

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.Security; namespace OpenAPITest.App_Start
{
public class ComHelper
{
/// <summary>
/// 获取post/get集合
/// </summary>
/// <param name="ignoreCase">true 不区分大小写,统一返回小写 false 区分大小写</param>
/// <returns></returns>
public static SortedDictionary<string, string> GetRequestSortDic(bool ignoreCase)
{
int i = ;
SortedDictionary<string, string> sArray = new SortedDictionary<string, string>();
NameValueCollection coll;
//Load Form variables into NameValueCollection variable.
coll = HttpContext.Current.Request.Form; //coll = HttpContext.Current.Request.Params; // Get names of all forms into a string array.
String[] requestItem = coll.AllKeys;
for (i = ; i < requestItem.Length; i++)
{
if (ignoreCase)
sArray.Add(requestItem[i].ToLower(), GetString(requestItem[i]));
else
sArray.Add(requestItem[i], GetString(requestItem[i]));
} coll = HttpContext.Current.Request.QueryString;
requestItem = coll.AllKeys;
for (i = ; i < requestItem.Length; i++)
{
if (ignoreCase)
sArray.Add(requestItem[i].ToLower(), GetString(requestItem[i]));
else
sArray.Add(requestItem[i], GetString(requestItem[i]));
} return sArray;
}
/// <summary>
/// 从当前环境中获取
/// </summary>
/// <param name="name"></param>
/// <param name="defValue"></param>
/// <returns></returns>
public static string GetString(string name)
{
string res = "";
var v = HttpContext.Current.Request[name];
if (v != null)
{
res = v.ToString();
}
return res;
} /// <summary>
/// 时间戳转为C#格式时间
/// </summary>
/// <param name="timeStamp"></param>
/// <returns></returns>
public static DateTime ConvertIntDateTime(string timeStamp)
{
DateTime dtStart = TimeZone.CurrentTimeZone.ToLocalTime(new DateTime(, , ));
long lTime = long.Parse(timeStamp + "");
TimeSpan toNow = new TimeSpan(lTime); return dtStart.Add(toNow);
}
/// <summary>
/// MD5加密
/// </summary>
/// <param name="str">原串</param>
/// <param name="code">加密位</param>
/// <returns></returns>
public static string ToMD5(string str)
{
return FormsAuthentication.HashPasswordForStoringInConfigFile(str, "MD5").ToLower();
} /// <summary>
/// 获取Sign
/// </summary>
/// <param name="inputPara"></param>
/// <param name="privateKey"></param>
/// <returns></returns>
public static string GetResponseMysign(SortedDictionary<string, string> inputPara, string privateKey)
{
string fullstring = GetPostStrings(inputPara, "_sign") + privateKey;
return ToMD5(fullstring);
}
private static string GetPostStrings(SortedDictionary<string, string> inputPara, string excepted)
{
Dictionary<string, string> sPara = new Dictionary<string, string>(); //过滤空值、sign与sign_type参数
foreach (KeyValuePair<string, string> temp in inputPara)
{
if (temp.Key.ToLower() != excepted && temp.Value != "" && temp.Value != null)
{
sPara.Add(temp.Key.ToLower(), temp.Value);
}
} //获得签名结果
StringBuilder prestr = new StringBuilder();
foreach (KeyValuePair<string, string> temp in sPara)
{
prestr.Append(temp.Key + "=" + temp.Value + "&");
} //去掉最後一個&字符
int nLen = prestr.Length;
if (nLen > )
prestr.Remove(nLen - , );
return prestr.ToString();
} /// <summary>
/// 获取十位的时间戳
/// </summary>
/// <param name="dt"></param>
/// <returns></returns>
public static string GenerateTimeStamp(DateTime dt)
{
// Default implementation of UNIX time of the current UTC time
TimeSpan ts = dt.ToUniversalTime() - new DateTime(, , , , , , );
return Convert.ToInt64(ts.TotalSeconds).ToString();
} }
}

ComHelper

2、Sign签名

(1)、sign的生成规则:服务器端接口中的所有参数+Uid+Ts,去除掉参数中值为空的参数后, 按照参数key值排序,用&链接,并全部转化为小写,然后用MD5加密,通过HttpHeader发送到服务器端接口。

生成Sign大代码如下(C#),Android和IOS可以同理生成

假如手机客户端请求的一个API接口为:http://weapi.com/order/getlist?StatusID=1&CarID=2&CityID=3&name=&key=222

sign=md5(carid=&cityid=&key=&statusid=&ts=&uid=)

(2)、验证客户端的Sign,防止参数被修改

 //请求签名,客户端生成的签名
var Sign = requestHeader.Get("Sign");
//排序字典,按照key排序
SortedDictionary<string, string> postValue = null;
//获取请求中所有的参数
postValue = ComHelper.GetRequestSortDic(true);
postValue.Add("Uid", Uid);//API账户名称
postValue.Add("Ts", Ts);//10位时间戳 string APIPrivateKey = "2D7E7C96-DAC5-4526-96C3-C60CDEC4B120";//客户端和手机端保持一致
//服务器端生成的Sign
string mysign = ComHelper.GetResponseMysign(postValue, APIPrivateKey);
if (!Sign.Equals(mysign, StringComparison.InvariantCultureIgnoreCase))
{
var resp = Request.CreateResponse<string>(HttpStatusCode.OK, "签名错误", "application/json"); ; throw new HttpResponseException(resp);
}

3、模拟测试

(1)C#模拟Http请求,代码如下

 //请求的API地址
string url = "http://localhost:51942/api/Values/Get?StatusID=1&CarID=2&CityID=3&name=&key=1233";
//生成Ts
string Ts = ComHelper.GenerateTimeStamp(DateTime.Now);
//SortedDictionary会自动按照key值排序
SortedDictionary<string, string> sortDic = new SortedDictionary<string, string>();
sortDic.Add("StatusID", "");
sortDic.Add("CarID", "");
sortDic.Add("CityID", "");
sortDic.Add("name", "");
sortDic.Add("key", "");
sortDic.Add("Uid","");
sortDic.Add("Ts", Ts);
string APIPrivateKey = "2D7E7C96-DAC5-4526-96C3-C60CDEC4B120";//客户端和手机端保持一致,md5加密多使用了一个参数
//获取Sign签名
string Sign = ComHelper.GetResponseMysign(sortDic, APIPrivateKey); //发送请求
System.Net.WebRequest wReq = System.Net.WebRequest.Create(url);
wReq.Headers.Add("Uid", "");
wReq.Headers.Add("Ts", Ts);
wReq.Headers.Add("Sign", Sign); System.Net.WebResponse wResp = wReq.GetResponse();
System.IO.Stream respStream = wResp.GetResponseStream(); using (System.IO.StreamReader reader = new System.IO.StreamReader(respStream, Encoding.UTF8))
{
string res= reader.ReadToEnd();
}

(2)服务器端验证参数,参数验证写在BaseApiController.cs文件中,只要继承该类的都可以验证客户端传过来的参数

 public class ValuesController : BaseApiController
{ // GET api/values
public IEnumerable<string> Get(string StatusID,string CarID,string CityID,string name, string key)
{
return new string[] { "value1", "value2" };
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http;
using System.Web.Http.Controllers;
using System.Net.Http;
using System.Net;
namespace OpenAPITest.App_Start
{
public class BaseApiController : ApiController
{
/// <summary>
/// 初始化方法
/// </summary>
/// <param name="controllerContext"></param>
protected override void Initialize(HttpControllerContext controllerContext)
{
base.Initialize(controllerContext);
//获取请求头信息
var requestHeader = HttpContext.Current.Request.Headers;
//登录用户的ID
var Uid = requestHeader.Get("Uid");
//请求签名
var Sign = requestHeader.Get("Sign");
//10位时间戳
var Ts = requestHeader.Get("Ts"); //验证请求头信息是否合法
string errorMsg = CheckRequestHeader(Sign, Ts, Uid);
//抛出错误信息
if (!string.IsNullOrWhiteSpace(errorMsg))
{
var resp = Request.CreateResponse<string>(HttpStatusCode.OK, "认证失败", "application/json"); ;
throw new HttpResponseException(resp);
}
//验证Ts是否合法(请求时间有效时间为:加减5分钟)
var ts = Ts;//10位时间戳
if (ts.Length != )
{
var resp = Request.CreateResponse<string>(HttpStatusCode.OK, "请求已过期", "application/json"); ; throw new HttpResponseException(resp);
}
var tsDate = ComHelper.ConvertIntDateTime(ts.ToString());
if (tsDate > DateTime.Now.AddMinutes() || tsDate < DateTime.Now.AddMinutes(-))
{
var resp = Request.CreateResponse<string>(HttpStatusCode.OK, "请求已过期", "application/json"); ; throw new HttpResponseException(resp);
} //排序字典,按照key排序
SortedDictionary<string, string> postValue = null;
//获取请求中所有的参数
postValue = ComHelper.GetRequestSortDic(true);
postValue.Add("Uid", Uid);//API账户名称
postValue.Add("Ts", Ts);//10位时间戳 string APIPrivateKey = "2D7E7C96-DAC5-4526-96C3-C60CDEC4B120";//客户端和手机端保持一致
//服务器端生成的Sign
string mysign = ComHelper.GetResponseMysign(postValue, APIPrivateKey);
if (!Sign.Equals(mysign, StringComparison.InvariantCultureIgnoreCase))
{
var resp = Request.CreateResponse<string>(HttpStatusCode.OK, "签名错误", "application/json"); ; throw new HttpResponseException(resp);
}
//验证通过 } /// <summary>
/// 验证请求头
/// </summary>
/// <param name="Sign"></param>
/// <param name="Ts"></param>
/// <param name="Pid"></param>
/// <returns></returns>
private string CheckRequestHeader(string Sign, string Ts, string Uid)
{
string ErrorMsg = "";
//请求签名
if (string.IsNullOrWhiteSpace(Sign))
{
ErrorMsg = "认证失败";
}
//10位时间戳
else if (string.IsNullOrWhiteSpace(Ts))
{
ErrorMsg = "认证失败";
}
//API账户名称
else if (string.IsNullOrWhiteSpace(Uid))
{
ErrorMsg = "认证失败";
}
return ErrorMsg;
}
}
}

BaseApiController

作者:Eric.Chen
出处:https://www.cnblogs.com/lc-chenlong

如果喜欢作者的文章,请关注“写代码的猿”订阅号以便第一时间获得最新内容。本文版权归作者所有,欢迎转载

WebApi与手机客户端通信安全机制的更多相关文章

  1. 实现手机扫描二维码页面登录,类似web微信-第三篇,手机客户端

    转自:http://www.cnblogs.com/fengyun99/p/3541254.html 上一篇,介绍了二维码生成的机制,紧接着,我们就要开发手机客户端来识别这个二维码. 二维码,实际上是 ...

  2. appium 九宫格解锁招商银行手机客户端app

    之前研究了一段时间的appium for native app 相应的总结如下:                                            appium测试环境搭建 :ht ...

  3. GPS部标平台的架构设计(六)-Android手机客户端和手机查车设计

    对于GPS软件平台,虽然有功能非常丰富的PC端或BS客户端,但是客户也是需要移动客户端来作为自己的辅助工具,也是需要的.做为GPS平台的设计者和开发者,在开发移动客户端的时候,也需要从常规的服务器开发 ...

  4. 【WP 8.1开发】手机客户端应用接收推送通知

    上一篇文章中,已经完成了用于发送通知的服务器端,接下来我们就用这个服务端来测试一下. 在开始测试之前,我们要做一个接收通知的WP应用. 1.启动VS Express for Windows,新建项目, ...

  5. DELPHI XE5开发WEB服务器及安卓手机客户端

    Xe5开发web服务端和手机客户端 ------------------------------------- Delphi xe5作为最新开发利器,就类似如当年的DELPHI,功能强大,快发速度快, ...

  6. android外包公司——最新案例铁血军事手机客户端(IOS & Android)

    <铁血军事>Android手机客户端由铁血网开发和运营,为网友提供铁血论坛和铁血读书两大产品.使用Android手机客户端,您不仅可以阅读到最新军事资讯,随时参与精彩话题讨论,还可以在线阅 ...

  7. 完美高仿精仿京东商城手机客户端android版源码

    完美高仿精仿京东商城手机客户端android版源码,是从安卓教程网那边转载过来的,这款应用源码非常不错的,也是一个非常优秀的应用源码的,希望能够帮到学习的朋友. _js_op> <igno ...

  8. 一元云购完整源码 云购CMS系统 带安卓和ios手机客户端

    看起来不错的一套一元云购CMS源码,源码包里面带了安卓和ios手机客户端,手机客户端需要自己反编译.    这里不做功能和其它更多的介绍,可以自己下载后慢慢测试了解.    下面演示图为亲测截图< ...

  9. 【转】Android手机客户端关于二维码扫描的源码--不错

    原文网址:https://github.com/SkillCollege/QrCodeScan QrCodeScan 这是Android手机客户端关于二维码扫描的源码,使用了高效的ZBar解码库,并修 ...

随机推荐

  1. Python 前端之JQuery

    查找: 选择器 筛选器 操作: CSS 属性 文本 事件: 优化 扩展: Form表单验证 Ajax: 偷偷发请求 www.php100.com/manual/jquery http://blog.j ...

  2. Java第一天学习笔记整理

    一.关键字 java的关键字对java的编译器有特殊的意义,他们用来表示一种数据类型,或者表示程序的结构等,关键字不能用作变量名.方法名.类名.包名. 常见的关键字: 用于定义数据类型的关键字 cla ...

  3. BCB中获得RichEdit 默认行间距

    首先,这些功能支持RichEdit2.0 以上功能: 其次,用常规的方法是无法获得LineSpace 的: 你使用 EM_GETPARAFORMAT也得不到,你会发现dyLineSpacing 的值永 ...

  4. dubbo+zookeeper例子

    0.原理   Alibaba有好几个分布式框架,主要有:进行远程调用(类似于RMI的这种远程调用)的(dubbo.hsf),jms消息服务(napoli.notify),KV数据库(tair)等.这个 ...

  5. bash脚本编程之二 条件判断and 逻辑运算

    1.条件测试结构 1) if/then结构: 判断命令列表的退出码是否为0,0为成功. 如果if和then在条件判断的同一行上的话, 必须使用分号来结束if表达式: if和then都是关键字. 关键字 ...

  6. easyui里弹窗的两种表现形式

    easyui里弹窗的两种表现形式 博客分类: jQueryEasyUi   1.主JSP页面中描绘弹窗   <div id="centerDiv" data-options= ...

  7. mysql 索引分类

    在数据库表中,对字段建立索引可以大大提高查询速度.通过善用这些索引,可以令 MySQL的查询和运行更加高效.索引是快速搜索的关键.MySQL索引的建立对于MySQL的高效运行是很重要的.下面介绍几种常 ...

  8. scala 学习: 逆变和协变

    scala 逆变和协变的概念网上有很多解释, 总结一句话就是 参数是逆变的或者不变的,返回值是协变的或者不变的. 但是为什么是这样的? 协变: 当s 是A的子类, 那么func(s) 是func(A) ...

  9. AsyncTask下载网络图片的简单应用

    1.imageTest package lpc.com.asynctaskdemo; import android.app.Activity; import android.graphics.Bitm ...

  10. IQKeyboardManager在某个页面取消键盘上面的Toolbar

    使用IQKeyboardManager的时候,如果想在某个页面取消键盘上面的Toolbar,请使用如下方法,按控制器去操作 // 取消IQKeyboardManager Toolbar [[IQKey ...