(2)在请求头中添加timespan(时间戳),nonce(随机数),staffId(用户Id),signature(签名参数)
//加入头信息
request.Headers.Add("staffid", staffId.ToString()); //当前请求用户StaffId
request.Headers.Add("timestamp", timeStamp); //发起请求时的时间戳(单位:毫秒)
request.Headers.Add("nonce", nonce); //发起请求时的时间戳(单位:毫秒)
request.Headers.Add("signature", GetSignature(timeStamp,nonce,staffId,data)); //当前请求内容的数字签名
(3)根据请求参数计算本次请求的签名,用timespan+nonc+staffId+token+data(请求参数字符串)得到signStr签名字符串,然后再进行排序和MD5加密得到最终的signature签名字符串,添加到请求头中
private static string GetSignature(string timeStamp,string nonce,int staffId,string data)
{
Token token = null;
var resultMsg = GetSignToken(staffId);
if (resultMsg != null)
{
if (resultMsg.StatusCode == (int)StatusCodeEnum.Success)
{
token = resultMsg.Result;
}
else
{
throw new Exception(resultMsg.Data.ToString());
}
}
else
{
throw new Exception("token为null,员工编号为:" +staffId);
}
var hash = System.Security.Cryptography.MD5.Create();
//拼接签名数据
var signStr = timeStamp +nonce+ staffId + token.SignToken.ToString() + data;
//将字符串中字符按升序排序
var sortStr = string.Concat(signStr.OrderBy(c => c));
var bytes = Encoding.UTF8.GetBytes(sortStr);
//使用MD5加密
var md5Val = hash.ComputeHash(bytes);
//把二进制转化为大写的十六进制
StringBuilder result = new StringBuilder();
foreach (var c in md5Val)
{
result.Append(c.ToString("X2"));
}
return result.ToString().ToUpper();
}
(4) webapi接收到相应的请求,取出请求头中的timespan,nonc,staffid,signature 数据,根据timespan判断此次请求是否失效,根据staffid取出相应token判断token是否失效,根据请求类型取出对应的请求参数,然后服务器端按照同样的规则重新计算请求签名,判断和请求头中的signature数据是否相同,如果相同的话则是合法请求,正常返回数据,如果不相同的话,该请求可能被恶意篡改,禁止访问相应的数据,返回相应的错误信息
如下使用全局过滤器拦截所有api请求进行统一的处理
public class ApiSecurityFilter : ActionFilterAttribute
{
public override void OnActionExecuting(System.Web.Http.Controllers.HttpActionContext actionContext)
{
ResultMsg resultMsg = null;
var request = actionContext.Request;
string method = request.Method.Method;
string staffid = String.Empty, timestamp = string.Empty, nonce = string.Empty, signature = string.Empty;
int id = 0;
if (request.Headers.Contains("staffid"))
{
staffid = HttpUtility.UrlDecode(request.Headers.GetValues("staffid").FirstOrDefault());
}
if (request.Headers.Contains("timestamp"))
{
timestamp = HttpUtility.UrlDecode(request.Headers.GetValues("timestamp").FirstOrDefault());
}
if (request.Headers.Contains("nonce"))
{
nonce = HttpUtility.UrlDecode(request.Headers.GetValues("nonce").FirstOrDefault());
}
if (request.Headers.Contains("signature"))
{
signature = HttpUtility.UrlDecode(request.Headers.GetValues("signature").FirstOrDefault());
}
//GetToken方法不需要进行签名验证
if (actionContext.ActionDescriptor.ActionName == "GetToken")
{
if (string.IsNullOrEmpty(staffid) || (!int.TryParse(staffid, out id) || string.IsNullOrEmpty(timestamp) || string.IsNullOrEmpty(nonce)))
{
resultMsg = new ResultMsg();
resultMsg.StatusCode = (int)StatusCodeEnum.ParameterError;
resultMsg.Info = StatusCodeEnum.ParameterError.GetEnumText();
resultMsg.Data = "";
actionContext.Response = HttpResponseExtension.toJson(JsonConvert.SerializeObject(resultMsg));
base.OnActionExecuting(actionContext);
return;
}
else
{
base.OnActionExecuting(actionContext);
return;
}
}
//判断请求头是否包含以下参数
if (string.IsNullOrEmpty(staffid) || (!int.TryParse(staffid, out id) || string.IsNullOrEmpty(timestamp) || string.IsNullOrEmpty(nonce) || string.IsNullOrEmpty(signature)))
{
resultMsg = new ResultMsg();
resultMsg.StatusCode = (int)StatusCodeEnum.ParameterError;
resultMsg.Info = StatusCodeEnum.ParameterError.GetEnumText();
resultMsg.Data = "";
actionContext.Response = HttpResponseExtension.toJson(JsonConvert.SerializeObject(resultMsg));
base.OnActionExecuting(actionContext);
return;
}
//判断timespan是否有效
double ts1 = 0;
double ts2 = (DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0)).TotalMilliseconds;
bool timespanvalidate = double.TryParse(timestamp, out ts1);
double ts = ts2 - ts1;
bool falg = ts > int.Parse(WebSettingsConfig.UrlExpireTime) * 1000;
if (falg || (!timespanvalidate))
{
resultMsg = new ResultMsg();
resultMsg.StatusCode = (int)StatusCodeEnum.URLExpireError;
resultMsg.Info = StatusCodeEnum.URLExpireError.GetEnumText();
resultMsg.Data = "";
actionContext.Response = HttpResponseExtension.toJson(JsonConvert.SerializeObject(resultMsg));
base.OnActionExecuting(actionContext);
return;
}
//判断token是否有效
Token token = (Token)HttpRuntime.Cache.Get(id.ToString());
string signtoken = string.Empty;
if (HttpRuntime.Cache.Get(id.ToString()) == null)
{
resultMsg = new ResultMsg();
resultMsg.StatusCode = (int)StatusCodeEnum.TokenInvalid;
resultMsg.Info = StatusCodeEnum.TokenInvalid.GetEnumText();
resultMsg.Data = "";
actionContext.Response = HttpResponseExtension.toJson(JsonConvert.SerializeObject(resultMsg));
base.OnActionExecuting(actionContext);
return;
}
else
{
signtoken = token.SignToken.ToString();
}
//根据请求类型拼接参数
NameValueCollection form = HttpContext.Current.Request.QueryString;
string data = string.Empty;
switch (method)
{
case "POST":
Stream stream = HttpContext.Current.Request.InputStream;
string responseJson = string.Empty;
StreamReader streamReader = new StreamReader(stream);
data = streamReader.ReadToEnd();
break;
case "GET":
//第一步:取出所有get参数
IDictionary<string, string> parameters = new Dictionary<string, string>();
for (int f = 0; f < form.Count; f++)
{
string key = form.Keys[f];
parameters.Add(key, form[key]);
}
// 第二步:把字典按Key的字母顺序排序
IDictionary<string, string> sortedParams = new SortedDictionary<string, string>(parameters);
IEnumerator<KeyValuePair<string, string>> dem = sortedParams.GetEnumerator();
// 第三步:把所有参数名和参数值串在一起
StringBuilder query = new StringBuilder();
while (dem.MoveNext())
{
string key = dem.Current.Key;
string value = dem.Current.Value;
if (!string.IsNullOrEmpty(key))
{
query.Append(key).Append(value);
}
}
data = query.ToString();
break;
default:
resultMsg = new ResultMsg();
resultMsg.StatusCode = (int)StatusCodeEnum.HttpMehtodError;
resultMsg.Info = StatusCodeEnum.HttpMehtodError.GetEnumText();
resultMsg.Data = "";
actionContext.Response = HttpResponseExtension.toJson(JsonConvert.SerializeObject(resultMsg));
base.OnActionExecuting(actionContext);
return;
}
bool result = SignExtension.Validate(timestamp, nonce, id, signtoken,data, signature);
if (!result)
{
resultMsg = new ResultMsg();
resultMsg.StatusCode = (int)StatusCodeEnum.HttpRequestError;
resultMsg.Info = StatusCodeEnum.HttpRequestError.GetEnumText();
resultMsg.Data = "";
actionContext.Response = HttpResponseExtension.toJson(JsonConvert.SerializeObject(resultMsg));
base.OnActionExecuting(actionContext);
return;
}
else
{
base.OnActionExecuting(actionContext);
}
}
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
base.OnActionExecuted(actionExecutedContext);
}
}
然后我们进行测试,检验api请求的合法性
Get请求:
1.获取产品数据,传递参数id=1,name="wahaha" ,完整请求为http://localhost:14826/api/product/getproduct?id=1&name=wahaha
2.请求头添加timespan,staffid,nonce,signature字段
3.如图当data里面的值为id1namewahaha的时候请求头中的signature和服务器端计算出来的result的值是完全一样的,当我将data修改为id1namewahaha1之后,服务器端计算出来的签名result和请求头中提交的signature就不相同了,就表示为不合法的请求了
4.不合法的请求就会被识别为请求参数已被修改
合法的请求则会返回对应的商品信息
post请求:
1.post对象序列化为json字符串后提交到后台,后台返回相应产品信息
2.后台获取请求的参数信息
3.判断签名是否成功,第一次请求签名参数signature和服务器端计算result完全相同, 然后当把请求参数中count的数量从10改成100之后服务器端计算的result和请求签名参数signature不同,所以请求不合法,是非法请求,同理如果其他任何参数被修改最后计算的结果都会和签名参数不同,请求同样识别为不合法请求
总结:
通过上面的案例,我们可以看出,安全的关键在于参与签名的TOKEN,整个过程中TOKEN是不参与通信的,所以只要保证TOKEN不泄露,请求就不会被伪造。
然后我们通过timestamp时间戳用来验证请求是否过期,这样就算被人拿走完整的请求链接也是无效的。
Sign签名的方式能够在一定程度上防止信息被篡改和伪造,保障通信的安全