zoukankan      html  css  js  c++  java
  • WebApi与手机客户端通信安全机制

    最近公司有几个项目需要开发手机客户端,服务器端选用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(1970, 1, 1, 0, 0, 0, 0);
                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 != 10)
                {
                    var resp = Request.CreateResponse<string>(HttpStatusCode.OK, "请求已过期", "application/json"); ;
    
                    throw new HttpResponseException(resp);
                }
                var tsDate = ComHelper.ConvertIntDateTime(ts.ToString());
                if (tsDate > DateTime.Now.AddMinutes(5) || tsDate < DateTime.Now.AddMinutes(-5))
                {
                    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 = 0;
                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 = 0; 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 = 0; 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(1970, 1, 1));
                long lTime = long.Parse(timeStamp + "0000000");
                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 > 1)
                    prestr.Remove(nLen - 1, 1);
                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(1970, 1, 1, 0, 0, 0, 0);
                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=2&cityid=3&key=222&statusid=1&ts=0123456789&uid=110)

    (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", "1");
                sortDic.Add("CarID", "2");
                sortDic.Add("CityID", "3");
                sortDic.Add("name", "");
                sortDic.Add("key", "1233");
                sortDic.Add("Uid","110");
                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", "110");
                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 != 10)
                {
                    var resp = Request.CreateResponse<string>(HttpStatusCode.OK, "请求已过期", "application/json"); ;
    
                    throw new HttpResponseException(resp);
                }
                var tsDate = ComHelper.ConvertIntDateTime(ts.ToString());
                if (tsDate > DateTime.Now.AddMinutes(5) || tsDate < DateTime.Now.AddMinutes(-5))
                {
                    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
    如果喜欢作者的文章,请关注“写代码的猿”订阅号以便第一时间获得最新内容。本文版权归作者所有,欢迎转载
  • 相关阅读:
    [大山中学模拟赛] 2016.9.17
    [DP优化方法之斜率DP]
    Gengxin讲STL系列——String
    小班讲课之动态规划基础背包问题
    ubuntu安装体验
    小班出题之字符串基础检测
    G
    B
    小项目--反eclass
    树--天平问题
  • 原文地址:https://www.cnblogs.com/lc-chenlong/p/4895537.html
Copyright © 2011-2022 走看看