首先,写这篇文章的原因是因为最近某一个项目中的接口被人为调用了,导致了数据库数据被串改。虽然是内部人无意点的,但还是引起了我的担忧,所有整理了下关于Webapi的相关签名机制。

一、我们在开发接口时,有时候嫌麻烦就懒进行相关的验证或只进行一些简单的验证,这样客户端就可以直接调用:如

调用Webapi接口:http://XXX.XXX.XX.XXX:8123/Token/GetTest?ID=123456

这种方式简单粗暴,在浏览器直接输入"http://XXX.XXX.XX.XXX:8123/Token/GetTest?ID=123456",即可获取产品列表信息了,但是这样的方式会存在很严重的安全性问题,没有进行任何的验证,大家都可以通过这个方法获取到产品列表,导致产品信息泄露,下面简单记录下使用使用TOKEN+签名认证

二、使用TOKEN+签名认证 保证请求安全性

  token+签名认证的主要原理是:1.做一个认证服务,提供一个认证的webapi,用户先访问它获取对应的token

2.用户拿着相应的token以及请求的参数和服务器端提供的签名算法计算出签名后再去访问指定的api

              3.服务器端每次接收到请求就获取对应用户的token和请求参数,服务器端再次计算签名和客户端签名做对比,如果验证通过则正常访问相应的api,验证失败则 返回具体的失败信息

具体代码如下:

1.用户请求认证服务GetToken,将token保存在服务器端缓存中,并返回对应的Token到客户端(该请求不需要进行签名认证),使用GET调用方式

[HttpGet]
public IHttpActionResult GetToken(string signKey)
{
if (string.IsNullOrEmpty(signKey))
return Json<ResultMsg>(new ResultMsg((int)ExceptionStatus.ParameterError, EnumExtension.GetEnumText(ExceptionStatus.ParameterError), null));
//根据签名ID获取缓存token
string strKey = string.Format("{0}{1}", WebConfig.signKey, signKey);
Token cacheData = HttpRuntime.Cache.Get(strKey) as Token;
if (cacheData == null)
{
cacheData = new Token();
cacheData.signId = signKey;
cacheData.timespan = DateTime.Now.AddDays();
cacheData.signToken = Guid.NewGuid().ToString("N");
//插入缓存,缓存时间为1天
HttpRuntime.Cache.Insert(strKey, cacheData, null, cacheData.timespan, TimeSpan.Zero);
}
//返回token信息
return Json<ResultMsg>(new ResultMsg((int)ExceptionStatus.OK, EnumExtension.GetEnumText(ExceptionStatus.OK), cacheData));
}

2.客户端调用方法,GET或POST

(1) GET:需要在请求头中添加:timespan(时间戳),nonce(随机数),signKey(key),signature(签名参数)

     public static T Get<T>(string url, string paras, string signId,bool isSign=true)
{
HttpWebRequest webrequest = null;
HttpWebResponse webresponse = null;
string strResult = string.Empty;
try
{
webrequest = (HttpWebRequest)WebRequest.Create(url + "?" + paras);
webrequest.Method = "GET";
webrequest.ContentType = "application/json";
webrequest.Timeout = ;
//加入头信息
string timespan = GetTimespan();
string ran = GetRandom();
webrequest.Headers.Add("signKey", signId);
DbLogger.LogWriteMessage("signKey:" + signId);
webrequest.Headers.Add("timespan", timespan);
DbLogger.LogWriteMessage("timespan:" + timespan);
webrequest.Headers.Add("nonce", ran);
DbLogger.LogWriteMessage("nonce:" + ran);
if (isSign)
{
string strSign = GetSignature(signId, timespan, ran, paras);
webrequest.Headers.Add("signature", strSign);
DbLogger.LogWriteMessage("signature:" + strSign);
}
webresponse = (HttpWebResponse)webrequest.GetResponse();
Stream stream = webresponse.GetResponseStream();
StreamReader sr = new StreamReader(stream, Encoding.UTF8);
strResult = sr.ReadToEnd();
}
catch (Exception ex)
{
return JsonConvert.DeserializeObject<T>(ex.Message);
}
finally
{
if (webresponse != null)
webresponse.Close();
if (webrequest != null)
webrequest.Abort();
}
return JsonConvert.DeserializeObject<T>(strResult);
}

(2)POST写法这里就不写了,同理需要设置header请求头参数:timespan(时间戳),nonce(随机数),signKey(key),signature(签名参数)

(3)根据请求参数计算本次请求的签名,用timespan+nonc+signKey+token+data(请求参数字符串)得到signStr签名字符串,然后再进行排序和MD5加密得到最终的signature签名字符串,添加到请求头中

     public static string GetSignature(string signKey, string timespan, string nonce, string data)
{
string signToken = string.Empty;
var result = GetToken<JObject>();
if (result != null)
{
if (result["code"].ToString() == "")
{
var tokena = JsonConvert.DeserializeObject<JObject>(result["result"].ToString());
if (tokena != null)
signToken = tokena["signToken"].ToString();
}
} var hash = MD5.Create();
string str = signKey + timespan + nonce + signToken + data;
byte[] bytes = Encoding.UTF8.GetBytes(string.Concat(str.OrderBy(c => c)));
DbLogger.LogWriteMessage("str内容:" + string.Concat(str.OrderBy(c => c)));
//使用MD5加密
var md5Val = hash.ComputeHash(bytes);
//把二进制转化为大写的十六进制
StringBuilder strSign = new StringBuilder();
foreach (var val in md5Val)
{
strSign.Append(val.ToString("X2"));
}
return strSign.ToString();
}

(4)Webapi接收到相应参数,通过header获取到timespan(时间戳),nonce(随机数),signKey(key),signature(签名参数),判断参数是否为空、接口是否在有效时间内、判断token是否有效、判断和请求的signature(签名)是否相同,如果通过,返回正常的结果。如果验证不通过,返回相应的错误提示信息。

     public override void OnActionExecuting(System.Web.Http.Controllers.HttpActionContext filterContext)
{
ResultMsg result = null;
string signKey = string.Empty, timespan = string.Empty, nonce = string.Empty, signature = string.Empty;
//判断请求的消息中是否包括判断参数
var request = filterContext.Request;
if (request.Headers.Contains("signKey"))
signKey = request.Headers.GetValues("signKey").FirstOrDefault();
if (request.Headers.Contains("timespan"))
timespan = request.Headers.GetValues("timespan").FirstOrDefault();
if (request.Headers.Contains("nonce"))
nonce = request.Headers.GetValues("nonce").FirstOrDefault();
if (request.Headers.Contains("signature"))
signature = request.Headers.GetValues("signature").FirstOrDefault(); //如果方法是GetToken,则不需要验证
if (filterContext.ActionDescriptor.ActionName.ToLower() == "gettoken")
{
if (string.IsNullOrEmpty(signKey) || string.IsNullOrEmpty(timespan) || string.IsNullOrEmpty(nonce))
{
result = new ResultMsg((int)ExceptionStatus.ParameterError, EnumExtension.GetEnumText(ExceptionStatus.ParameterError), null);
filterContext.Response = HttpResponseExtension.ToJson(result);
base.OnActionExecuting(filterContext);
return;
}
else
{
base.OnActionExecuting(filterContext);
return;
}
}
DbLogger.LogWriteMessage("测试参数");
string signtoken = string.Empty;
//判断是否包含以下参数
if (string.IsNullOrEmpty(signKey) || string.IsNullOrEmpty(timespan) || string.IsNullOrEmpty(nonce) || string.IsNullOrEmpty(signature))
{
result = new ResultMsg((int)ExceptionStatus.ParameterError, EnumExtension.GetEnumText(ExceptionStatus.ParameterError), null);
filterContext.Response = HttpResponseExtension.ToJson(result);
base.OnActionExecuting(filterContext);
return;
} DbLogger.LogWriteMessage("测试是否在有效时间内");
//判断是否在有效时间内
double ts1 = ;
double ts2 = (DateTime.UtcNow - new DateTime(, , , , , )).TotalMilliseconds;
bool timespanValidate = double.TryParse(timespan, out ts1);
double ts = ts2 - ts1;
bool falg = ts > int.Parse(WebConfig.UrlExpireTime) * ;
if (!timespanValidate || falg)
{
result = new ResultMsg((int)ExceptionStatus.URLExpireError, EnumExtension.GetEnumText(ExceptionStatus.URLExpireError), null);
filterContext.Response = HttpResponseExtension.ToJson(result);
base.OnActionExecuting(filterContext);
return;
} DbLogger.LogWriteMessage("测试token是否有效");
//判断token是否有效
Token token = HttpRuntime.Cache.Get(string.Format("{0}{1}", WebConfig.signKey, signKey)) as Token;
if (token == null)
{
result = new ResultMsg((int)ExceptionStatus.TokenInvalid, EnumExtension.GetEnumText(ExceptionStatus.TokenInvalid), null);
filterContext.Response = HttpResponseExtension.ToJson(result);
base.OnActionExecuting(filterContext);
return;
}
else
signtoken = token.signToken; DbLogger.LogWriteMessage("判断http调用方式");
string data = string.Empty;
//判断http调用方式
string method = request.Method.Method.ToUpper();
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":
NameValueCollection form = HttpContext.Current.Request.QueryString;
//第一步:取出所有get参数
IDictionary<string, string> parameters = new Dictionary<string, string>();
for (int f = ; 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:
result = new ResultMsg((int)ExceptionStatus.HttpMehtodError, EnumExtension.GetEnumText(ExceptionStatus.HttpMehtodError), null);
filterContext.Response = HttpResponseExtension.ToJson(result);
base.OnActionExecuting(filterContext);
break;
} DbLogger.LogWriteMessage("验证签名信息是否符合");
//验证签名信息是否符合
bool valida = ValidateSign.Validate(signKey, timespan, nonce, signtoken, data, signature);
if (!valida)
{
result = new ResultMsg((int)ExceptionStatus.HttpRequestError, EnumExtension.GetEnumText(ExceptionStatus.HttpRequestError), null);
filterContext.Response = HttpResponseExtension.ToJson(result);
base.OnActionExecuting(filterContext);
return;
}
else
base.OnActionExecuting(filterContext);
}
}

下面我们进行测试:

GET请求:

返回结果:

但我们在浏览器中直接显示或信息被串改时,不合法的请求就会被识别为请求参数已被修改

判断签名是否成功,第一次请求签名参数signature和服务器端计算result完全相同, 然后当把请求参数修改之后服务器端计算的result和请求签名参数signature不同,所以请求不合法,是非法请求,同理如果其他任何参数被修改最后计算的结果都会和签名参数不同,请求同样识别为不合法请求

总结:

通过上面的案例,我们可以看出,安全的关键在于参与签名的token,整个过程中token是不参与通信的,所以只要保证token不泄露,请求就不会被伪造。

然后我们通过timestamp时间戳用来验证请求是否过期,这样就算被人拿走完整的请求链接也是无效的。

源码下载地址:https://pan.baidu.com/s/1hrBOnRY

记录下Webapi签名机制的更多相关文章

  1. 详解 WebAPI 签名机制

    首先,写这篇文章的原因是因为最近某一个项目中的接口被人为调用了,导致了数据库数据被串改.虽然是内部人无意点的,但还是引起了我的担忧,所有整理了下关于WebAPI的相关签名机制. 一.我们在开发接口时, ...

  2. 【转】漫谈iOS程序的证书和签名机制

    转自:漫谈iOS程序的证书和签名机制 接触iOS开发半年,曾经也被这个主题坑的摸不着头脑,也在淘宝上买过企业证书签名这些服务,有大神都做了一个全自动的发布打包(不过此大神现在不卖企业证书了),甚是羡慕 ...

  3. 漫谈iOS程序的证书和签名机制

    接触iOS开发半年,曾经也被这个主题坑的摸不着头脑,也在淘宝上买过企业证书签名这些服务,有大神都做了一个全自动的发布打包(不过此大神现在不卖企业证书了),甚是羡慕和崇拜.于是,花了一点时间去研究了一下 ...

  4. iOS开发——高级技术&签名机制

    签名机制 最近看了objc.io上第17期中的文章 <Inside Code Signing> 对应的中文翻译版 <代码签名探析> ,受益颇深,对iOS代码签名机制有了进一步的 ...

  5. iOS签名机制解析

    最近遇到一个签名的问题,借机把iOS签名相关知识点研究了一下.现总结如下:(研究过程中参考了这位仁兄的博客.很全面,本文也有部分借鉴) 非对称加密 这个是签名机制的算法基础.所谓非对称加密的是相对于对 ...

  6. Android签名机制之---签名过程详解

    http://www.2cto.com/kf/201512/455388.html 一.前言 又是过了好长时间,没写文章的双手都有点难受了.今天是圣诞节,还是得上班.因为前几天有一个之前的同事,在申请 ...

  7. Android签名机制之---签名验证过程具体解释

    一.前言 今天是元旦,也是Single Dog的嚎叫之日,仅仅能写博客来祛除寂寞了,今天我们继续来看一下Android中的签名机制的姊妹篇:Android中是怎样验证一个Apk的签名. 在前一篇文章中 ...

  8. Android签名机制之---签名验证过程详解

    一.前言 今天是元旦,也是Single Dog的嚎叫之日,只能写博客来祛除寂寞了,今天我们继续来看一下Android中的签名机制的姊妹篇:Android中是如何验证一个Apk的签名.在前一篇文章中我们 ...

  9. C#进阶系列——WebApi 路由机制剖析:你准备好了吗?

    前言:从MVC到WebApi,路由机制一直是伴随着这些技术的一个重要组成部分. 它可以很简单:如果你仅仅只需要会用一些简单的路由,如/Home/Index,那么你只需要配置一个默认路由就能简单搞定: ...

随机推荐

  1. 【专章】dp入门

    动态规划(简称dp),可以说是各种程序设计中遇到的第一个坎吧,这篇博文是我对dp的一点点理解,希望可以帮助更多人dp入门. ***实践是检验真理的唯一标准,看再多文章不如自己动手做几道!!!*** 先 ...

  2. 18.Llinux-触摸屏驱动(详解)

    本节的触摸屏驱动也是使用之前的输入子系统 1.先来回忆之前第12节分析的输入子系统 其中输入子系统层次如下图所示, 其中事件处理层的函数都是通过input_register_handler()函数注册 ...

  3. hdu4678 Mine 2013 Multi-University Training Contest 8 博弈题

    Mine Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65535/32768 K (Java/Others) Total Submi ...

  4. hdu1512 Monkey King(左偏树 + 并查集)

    Once in a forest, there lived N aggressive monkeys. At the beginning, they each does things in its o ...

  5. Spring详解(七)------事务管理

    PS:本篇博客源码下载链接:http://pan.baidu.com/s/1mi3NhX2 密码:3io2 1.事务介绍 事务(Transaction),一般是指要做的或所做的事情.在计算机术语中是指 ...

  6. @htmlhepler dropdownlistfor 报错

    说系统的字段不匹配. 是因为ViewData,没有赋值.

  7. 【Learning】多项式乘法与快速傅里叶变换(FFT)

    简介: FFT主要运用于快速卷积,其中一个例子就是如何将两个多项式相乘,或者高精度乘高精度的操作. 显然暴搞是$O(n^2)$的复杂度,然而FFT可以将其将为$O(n lg n)$. 这看起来十分玄学 ...

  8. Python 基础之函数、深浅copy,set及练习

    三元运算符通常在Python里被称为条件表达式,这些表达式基于真(true)/假(not)的条件判断,在Python 2.4以上才有了三元操作. 语法格式: X if C else Y 有了三元表达式 ...

  9. MySQL 高效查询

    在“现场加号&预约排队”项目中,“号贩子排查任务”在线下测试的时候没有问题,但是线上后,由于线上的数据量较大,导致在执行查询的时系统崩溃:后来经过查找,发现写的sql不合理,查出了许多用不到的 ...

  10. RESTful API 架构解读

    RESTful API 架构解读 首先我们还是先介绍下 RESTful api 的来龙去脉. 首先, RESTful (下文都简称 RESTful api 为 RESTful ) 1.RESTful ...