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;
}
}
}