首先,写这篇文章的原因是因为最近某一个项目中的接口被人为调用了,导致了数据库数据被串改。虽然是内部人无意点的,但还是引起了我的担忧,所有整理了下关于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. 为什么要用深度学习来做个性化推荐 CTR 预估

    欢迎大家前往腾讯云技术社区,获取更多腾讯海量技术实践干货哦~ 作者:苏博览 深度学习应该这一两年计算机圈子里最热的一个词了.基于深度学习,工程师们在图像,语音,NLP等领域都取得了令人振奋的进展.而深 ...

  2. OpenCV探索之路(二十五):制作简易的图像标注小工具

    搞图像深度学习的童鞋一定碰过图像数据标注的东西,当我们训练网络时需要训练集数据,但在网上又没有找到自己想要的数据集,这时候就考虑自己制作自己的数据集了,这时就需要对图像进行标注.图像标注是件很枯燥又很 ...

  3. java之装饰器模式

    Decorator Pattern(装饰器模式),定义:Attach additional responsibilities to an object dynamically. Decorators  ...

  4. 简单说明如何设置系统中的NLS_LANG环境变量

    概述:本地化是系统或软件运行的语言和文化环境.设置NLS_LANG环境参数是规定Oracle数据库软件本地化行为最简单的方式.NLS_LANG参数不但指定了客户端应用程序和Oracle数据库所使用的语 ...

  5. mysql慢查询分析工具 mysqlsla 安装

    概述 mysqlsla 是一款很不错的mysql慢查询日志分析工具,而且简单易用.mysqlsla 是用perl编写的脚本,运行需要perl-DBI和per-DBD-Mysql两模块的支持.mysql ...

  6. Unity3D - Animator Controller循环依赖

    问题 假设有2个Animator Controller,分别命名为TestControllerLhs.controller以及TestControllerRhs.controller.在TestCon ...

  7. JAVA提高三:反射总结

    为前期学习过反射,再这里再次复习总结下:[转载请说明来源:http://www.cnblogs.com/pony1223/p/7659210.html ] 一.透彻分析反射的基础_Class类 Cla ...

  8. WPF ListBox数据绑定

    本文来源 http://wshoufeng1989.blog.163.com/blog/static/202047033201282911633670/  风随影动的博客 使用数据库AllData , ...

  9. OpenGL ES2.0贴图

    1.定义传入着色器的顶点数据及索引 //传入结构体 typedef struct { ]; ]; } Vertex; //顶点数据 const Vertex Vertices[] = { {{, -, ...

  10. [Python]Codecombat攻略之地牢Kithgard(1-22关)

    首页:https://cn.codecombat.com/play语言:Python 第一界面:地牢 Kithgard(22关) 时间:1-3小时 内容:语法.方法.参数.字符串.循环.变量等 网页: ...