| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328 |
- 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;
- using Microsoft.AspNetCore.Http;
- 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.QueryString.ToUriComponent();
- 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>();
- if (request.ContentType != null && (request.ContentType.ToLower().Contains("form-data") || request.ContentType.ToLower().Contains("www-form")))
- {
- IFormCollection form = request.Form;
- var keys = form.Keys;
- 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.Body;
- 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.Query[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.Host.Host;
- 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;
- }
- }
- }
|