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 { /// /// SPI请求校验结果。 /// public class CheckResult { public bool Success { get; set; } public string Body { get; set; } } /// /// SPI服务提供方工具类。 /// 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"}; /// /// 校验SPI请求签名,不支持带上传文件的HTTP请求。 /// /// HttpRequest对象实例 /// APP密钥 /// 校验结果 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; } /// /// 校验SPI请求签名,适用于Content-Type为application/x-www-form-urlencoded或multipart/form-data的GET或POST请求。 /// /// 请求对象 /// app对应的secret /// true:校验通过;false:校验不通过 public static bool CheckSign4FormRequest(HttpRequest request, string secret) { return CheckSignInternal(request, null, secret); } /// /// 校验SPI请求签名,适用于Content-Type为text/xml或text/json的POST请求。 /// /// 请求对象 /// 请求体的文本内容 /// app对应的secret /// true:校验通过;false:校验不通过 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 parameters = new SortedDictionary(StringComparer.Ordinal); string charset = GetRequestCharset(request.ContentType); // 1. 获取header参数 AddAll(parameters, GetHeaderMap(request, charset)); // 2. 获取url参数 Dictionary 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 dest, IDictionary from) { if (from != null && from.Count > 0) { IEnumerator> em = from.GetEnumerator(); while (em.MoveNext()) { KeyValuePair kvp = em.Current; dest.Add(kvp.Key, kvp.Value); } } } /// /// 签名规则:hex(md5(secret+sorted(header_params+url_params+form_params)+body)+secret) /// private static string Sign(IDictionary parameters, string body, string secret, string charset) { IEnumerator> 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 GetHeaderMap(HttpRequest request, string charset) { Dictionary headerMap = new Dictionary(); 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 GetQueryMap(HttpRequest request, string charset) { Dictionary queryMap = new Dictionary(); 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 GetFormMap(HttpRequest request) { Dictionary formMap = new Dictionary(); 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(); } } /// /// 检查SPI请求到达服务器端是否已经超过指定的分钟数,如果超过则拒绝请求。 /// /// true代表不超过,false代表超过。 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; } } /// /// 检查发起SPI请求的来源IP是否是TOP机房的出口IP。 /// /// HTTP请求对象 /// TOP网关IP出口地址段列表,通过taobao.top.ipout.get获得 /// true表达IP来源合法,false代表IP来源不合法 public static bool CheckRemoteIp(HttpRequest request, List 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; } } }