| using System; | 
| using System.Collections; | 
| using System.Collections.Generic; | 
| using System.Collections.Specialized; | 
| using System.IO; | 
| using System.Security.Cryptography; | 
| using System.Text; | 
| using System.Web; | 
|   | 
| namespace Top.Api.Util | 
| { | 
|     /// <summary> | 
|     /// SPI请求校验结果。 | 
|     /// </summary> | 
|     public class CheckResult | 
|     { | 
|         public bool Success { get; set; } | 
|   | 
|         public string Body { get; set; } | 
|     } | 
|   | 
|     /// <summary> | 
|     /// SPI服务提供方工具类。 | 
|     /// </summary> | 
|     public class SpiUtils | 
|     { | 
|         private const string TOP_SIGN_LIST = "top-sign-list"; | 
|         private static readonly string[] HEADER_FIELDS_IP = {"X-Real-IP", "X-Forwarded-For", "Proxy-Client-IP", | 
|         "WL-Proxy-Client-IP", "HTTP_CLIENT_IP", "HTTP_X_FORWARDED_FOR"}; | 
|   | 
|         /// <summary> | 
|         /// 校验SPI请求签名,不支持带上传文件的HTTP请求。 | 
|         /// </summary> | 
|         /// <param name="request">HttpRequest对象实例</param> | 
|         /// <param name="secret">APP密钥</param> | 
|         /// <returns>校验结果</returns> | 
|         public static CheckResult CheckSign(HttpRequest request, string secret) | 
|         { | 
|             CheckResult result = new CheckResult(); | 
|             string ctype = request.ContentType; | 
|             if (ctype.StartsWith(Constants.CTYPE_APP_JSON) || ctype.StartsWith(Constants.CTYPE_TEXT_XML) || ctype.StartsWith(Constants.CTYPE_TEXT_PLAIN) || ctype.StartsWith(Constants.CTYPE_APPLICATION_XML)) | 
|             { | 
|                 result.Body = GetStreamAsString(request, GetRequestCharset(ctype)); | 
|                 result.Success = CheckSignInternal(request, result.Body, secret); | 
|             } | 
|             else if (ctype.StartsWith(Constants.CTYPE_FORM_DATA)) | 
|             { | 
|                 result.Success = CheckSignInternal(request, null, secret); | 
|             } | 
|             else | 
|             { | 
|                 throw new TopException("Unspported SPI request"); | 
|             } | 
|             return result; | 
|         } | 
|   | 
|         /// <summary> | 
|         /// 校验SPI请求签名,适用于Content-Type为application/x-www-form-urlencoded或multipart/form-data的GET或POST请求。 | 
|         /// </summary> | 
|         /// <param name="request">请求对象</param> | 
|         /// <param name="secret">app对应的secret</param> | 
|         /// <returns>true:校验通过;false:校验不通过</returns> | 
|         public static bool CheckSign4FormRequest(HttpRequest request, string secret) | 
|         { | 
|             return CheckSignInternal(request, null, secret); | 
|         } | 
|   | 
|         /// <summary> | 
|         /// 校验SPI请求签名,适用于Content-Type为text/xml或text/json的POST请求。 | 
|         /// </summary> | 
|         /// <param name="request">请求对象</param> | 
|         /// <param name="body">请求体的文本内容</param> | 
|         /// <param name="secret">app对应的secret</param> | 
|         /// <returns>true:校验通过;false:校验不通过</returns> | 
|         public static bool CheckSign4TextRequest(HttpRequest request, string body, string secret) | 
|         { | 
|             return CheckSignInternal(request, body, secret); | 
|         } | 
|   | 
|         private static bool CheckSignInternal(HttpRequest request, string body, string secret) | 
|         { | 
|             IDictionary<string, string> parameters = new SortedDictionary<string, string>(StringComparer.Ordinal); | 
|             string charset = GetRequestCharset(request.ContentType); | 
|   | 
|             // 1. 获取header参数 | 
|             AddAll(parameters, GetHeaderMap(request, charset)); | 
|   | 
|             // 2. 获取url参数 | 
|             Dictionary<string, string> queryMap = GetQueryMap(request, charset); | 
|             AddAll(parameters, queryMap); | 
|   | 
|             // 3. 获取form参数 | 
|             AddAll(parameters, GetFormMap(request)); | 
|   | 
|             // 4. 生成签名并校验 | 
|             string remoteSign = null; | 
|             if (queryMap.ContainsKey(Constants.SIGN)) | 
|             { | 
|                 remoteSign = queryMap[Constants.SIGN]; | 
|             } | 
|             string localSign = Sign(parameters, body, secret, charset); | 
|             return localSign.Equals(remoteSign); | 
|         } | 
|   | 
|         private static void AddAll(IDictionary<string, string> dest, IDictionary<string, string> from) | 
|         { | 
|             if (from != null && from.Count > 0) | 
|             { | 
|                 IEnumerator<KeyValuePair<string, string>> em = from.GetEnumerator(); | 
|                 while (em.MoveNext()) | 
|                 { | 
|                     KeyValuePair<string, string> kvp = em.Current; | 
|                     dest.Add(kvp.Key, kvp.Value); | 
|                 } | 
|             } | 
|         } | 
|   | 
|         /// <summary> | 
|         /// 签名规则:hex(md5(secret+sorted(header_params+url_params+form_params)+body)+secret) | 
|         /// </summary> | 
|         private static string Sign(IDictionary<string, string> parameters, string body, string secret, string charset) | 
|         { | 
|             IEnumerator<KeyValuePair<string, string>> em = parameters.GetEnumerator(); | 
|   | 
|             // 第1步:把所有参数名和参数值串在一起 | 
|             StringBuilder query = new StringBuilder(secret); | 
|             while (em.MoveNext()) | 
|             { | 
|                 string key = em.Current.Key; | 
|                 if (!Constants.SIGN.Equals(key)) | 
|                 { | 
|                     string value = em.Current.Value; | 
|                     query.Append(key).Append(value); | 
|                 } | 
|             } | 
|             if (body != null) | 
|             { | 
|                 query.Append(body); | 
|             } | 
|   | 
|             query.Append(secret); | 
|   | 
|             // 第2步:使用MD5加密 | 
|             MD5 md5 = MD5.Create(); | 
|             byte[] bytes = md5.ComputeHash(Encoding.GetEncoding(charset).GetBytes(query.ToString())); | 
|   | 
|             // 第3步:把二进制转化为大写的十六进制 | 
|             StringBuilder result = new StringBuilder(); | 
|             for (int i = 0; i < bytes.Length; i++) | 
|             { | 
|                 result.Append(bytes[i].ToString("X2")); | 
|             } | 
|   | 
|             return result.ToString(); | 
|         } | 
|   | 
|         private static string GetRequestCharset(string ctype) | 
|         { | 
|             string charset = "utf-8"; | 
|             if (!string.IsNullOrEmpty(ctype)) | 
|             { | 
|                 string[] entires = ctype.Split(';'); | 
|                 foreach (string entry in entires) | 
|                 { | 
|                     string _entry = entry.Trim(); | 
|                     if (_entry.StartsWith("charset")) | 
|                     { | 
|                         string[] pair = _entry.Split('='); | 
|                         if (pair.Length == 2) | 
|                         { | 
|                             if (!string.IsNullOrEmpty(pair[1])) | 
|                             { | 
|                                 charset = pair[1].Trim(); | 
|                             } | 
|                         } | 
|                         break; | 
|                     } | 
|                 } | 
|             } | 
|             return charset; | 
|         } | 
|   | 
|         public static Dictionary<string, string> GetHeaderMap(HttpRequest request, string charset) | 
|         { | 
|             Dictionary<string, string> headerMap = new Dictionary<string, string>(); | 
|             string signList = request.Headers[TOP_SIGN_LIST]; | 
|             if (!string.IsNullOrEmpty(signList)) | 
|             { | 
|                 string[] keys = signList.Split(','); | 
|                 foreach (string key in keys) | 
|                 { | 
|                     string value = request.Headers[key]; | 
|                     if (string.IsNullOrEmpty(value)) | 
|                     { | 
|                         headerMap.Add(key, ""); | 
|                     } | 
|                     else | 
|                     { | 
|                         headerMap.Add(key, HttpUtility.UrlDecode(value, Encoding.GetEncoding(charset))); | 
|                     } | 
|                 } | 
|             } | 
|             return headerMap; | 
|         } | 
|   | 
|         public static Dictionary<string, string> GetQueryMap(HttpRequest request, string charset) | 
|         { | 
|             Dictionary<string, string> queryMap = new Dictionary<string, string>(); | 
|             string queryString = request.Url.Query; | 
|             if (!string.IsNullOrEmpty(queryString)) | 
|             { | 
|                 queryString = queryString.Substring(1); // 忽略?号 | 
|                 string[] parameters = queryString.Split('&'); | 
|                 foreach (string parameter in parameters) | 
|                 { | 
|                     string[] kv = parameter.Split('='); | 
|                     if (kv.Length == 2) | 
|                     { | 
|                         string key = HttpUtility.UrlDecode(kv[0], Encoding.GetEncoding(charset)); | 
|                         string value = HttpUtility.UrlDecode(kv[1], Encoding.GetEncoding(charset)); | 
|                         queryMap.Add(key, value); | 
|                     } | 
|                     else if (kv.Length == 1) | 
|                     { | 
|                         string key = HttpUtility.UrlDecode(kv[0], Encoding.GetEncoding(charset)); | 
|                         queryMap.Add(key, ""); | 
|                     } | 
|                 } | 
|             } | 
|             return queryMap; | 
|         } | 
|   | 
|         public static Dictionary<string, string> GetFormMap(HttpRequest request) | 
|         { | 
|             Dictionary<string, string> formMap = new Dictionary<string, string>(); | 
|             NameValueCollection form = request.Form; | 
|             string[] keys = form.AllKeys; | 
|             foreach (string key in keys) | 
|             { | 
|                 string value = request.Form[key]; | 
|                 if (string.IsNullOrEmpty(value)) | 
|                 { | 
|                     formMap.Add(key, ""); | 
|                 } | 
|                 else | 
|                 { | 
|                     formMap.Add(key, value); | 
|                 } | 
|             } | 
|             return formMap; | 
|         } | 
|   | 
|         public static string GetStreamAsString(HttpRequest request, string charset) | 
|         { | 
|             Stream stream = null; | 
|             StreamReader reader = null; | 
|   | 
|             try | 
|             { | 
|                 // 以字符流的方式读取HTTP请求体 | 
|                 stream = request.InputStream; | 
|                 reader = new StreamReader(stream, Encoding.GetEncoding(charset)); | 
|                 return reader.ReadToEnd(); | 
|             } | 
|             finally | 
|             { | 
|                 // 释放资源 | 
|                 if (reader != null) reader.Close(); | 
|                 if (stream != null) stream.Close(); | 
|             } | 
|         } | 
|   | 
|         /// <summary> | 
|         /// 检查SPI请求到达服务器端是否已经超过指定的分钟数,如果超过则拒绝请求。 | 
|         /// </summary> | 
|         /// <returns>true代表不超过,false代表超过。</returns> | 
|         public static bool CheckTimestamp(HttpRequest request, int minutes) | 
|         { | 
|             string ts = request.QueryString[Constants.TIMESTAMP]; | 
|             if (!string.IsNullOrEmpty(ts)) | 
|             { | 
|                 DateTime remote = DateTime.ParseExact(ts, Constants.DATE_TIME_FORMAT, null); | 
|                 DateTime local = DateTime.Now; | 
|                 return remote.AddMinutes(minutes).CompareTo(local) > 0; | 
|             } | 
|             else | 
|             { | 
|                 return false; | 
|             } | 
|         } | 
|   | 
|         /// <summary> | 
|         /// 检查发起SPI请求的来源IP是否是TOP机房的出口IP。 | 
|         /// </summary> | 
|         /// <param name="request">HTTP请求对象</param> | 
|         /// <param name="topIpList">TOP网关IP出口地址段列表,通过taobao.top.ipout.get获得</param> | 
|         /// <returns>true表达IP来源合法,false代表IP来源不合法</returns> | 
|         public static bool CheckRemoteIp(HttpRequest request, List<string> topIpList) | 
|         { | 
|             string ip = request.UserHostAddress; | 
|             foreach (string ipHeader in HEADER_FIELDS_IP) | 
|             { | 
|                 string realIp = request.Headers[ipHeader]; | 
|                 if (!string.IsNullOrEmpty(realIp) && !"unknown".Equals(realIp)) | 
|                 { | 
|                     ip = realIp; | 
|                     break; | 
|                 } | 
|             } | 
|   | 
|             if (topIpList != null) | 
|             { | 
|                 foreach (string topIp in topIpList) | 
|                 { | 
|                     if (StringUtil.IsIpInRange(ip, topIp)) | 
|                     { | 
|                         return true; | 
|                     } | 
|                 } | 
|             } | 
|             return false; | 
|         } | 
|     } | 
| } |