TOKEN+签名验证

首先问大家一个问题,你在写开放的API接口时是如何保证数据的安全性的?先来看看有哪些安全性问题在开放的api接口中,我们通过http Post或者Get方式请求服务器的时候,会面临着许多的安全性问题,例如:

  1. 请求来源(身份)是否合法?
  2. 请求参数被篡改?
  3. 请求的唯一性(不可复制),防止请求被恶意攻击

为了保证数据在通信时的安全性,我们可以采用TOKEN+参数签名的方式来进行相关验证。

比如说我们客户端需要查询产品信息这个操作来进行分析,客户端点击查询按钮==》调用服务器端api进行查询==》服务器端返回查询结果

一、不进行验证的方式

api查询接口:

客户端调用:http://api.XXX.com/getproduct?id=value1

如上,这种方式简单粗暴,在浏览器直接输入"http://api.XXX.com/getproduct?id=value1",即可获取产品列表信息了,但是这样的方式会存在很严重的安全性问题,没有进行任何的验证,大家都可以通过这个方法获取到产品列表,导致产品信息泄露。
那么,如何验证调用者身份呢?如何防止参数被篡改呢?如何保证请求的唯一性? 如何保证请求的唯一性,防止请求被恶意攻击呢?

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

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

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

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

具体代码如下 :

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

public HttpResponseMessage GetToken(string staffId)
{
ResultMsg resultMsg = null;
int id = 0; //判断参数是否合法
if (string.IsNullOrEmpty(staffId) || (!int.TryParse(staffId, out id)))
{
resultMsg = new ResultMsg();
resultMsg.StatusCode = (int)StatusCodeEnum.ParameterError;
resultMsg.Info = StatusCodeEnum.ParameterError.GetEnumText();
resultMsg.Data = "";
return HttpResponseExtension.toJson(JsonConvert.SerializeObject(resultMsg));
} //插入缓存
Token token =(Token)HttpRuntime.Cache.Get(id.ToString());
if (HttpRuntime.Cache.Get(id.ToString()) == null)
{
token = new Token();
token.StaffId = id;
token.SignToken = Guid.NewGuid();
token.ExpireTime = DateTime.Now.AddDays(1);
HttpRuntime.Cache.Insert(token.StaffId.ToString(), token, null, token.ExpireTime, TimeSpan.Zero);
} //返回token信息
resultMsg =new ResultMsg();
resultMsg.StatusCode = (int)StatusCodeEnum.Success;
resultMsg.Info = "";
resultMsg.Data = token; return HttpResponseExtension.toJson(JsonConvert.SerializeObject(resultMsg));
}

2.客户端调用服务器端API,需要对请求进行签名认证,签名方式如下

(1) get请求:按照请求参数名称将所有请求参数按照字母先后顺序排序得到:keyvaluekeyvalue...keyvalue  字符串如:将arong=1,mrong=2,crong=3 排序为:arong=1, crong=3,mrong=2  然后将参数名和参数值进行拼接得到参数字符串:arong1crong3mrong2。  

public static Tuple<string,string> GetQueryString(Dictionary<string, string> parames)
{
// 第一步:把字典按Key的字母顺序排序
IDictionary<string, string> sortedParams = new SortedDictionary<string, string>(parames);
IEnumerator<KeyValuePair<string, string>> dem = sortedParams.GetEnumerator(); // 第二步:把所有参数名和参数值串在一起
StringBuilder query = new StringBuilder(""); //签名字符串
StringBuilder queryStr = new StringBuilder(""); //url参数
if (parames == null || parames.Count == 0)
return new Tuple<string,string>("",""); while (dem.MoveNext())
{
string key = dem.Current.Key;
string value = dem.Current.Value;
if (!string.IsNullOrEmpty(key))
{
query.Append(key).Append(value);
queryStr.Append("&").Append(key).Append("=").Append(value);
}
} return new Tuple<string, string>(query.ToString(), queryStr.ToString().Substring(1, queryStr.Length - 1));
}

post请求:将请求的参数对象序列化为json格式字符串   

 Product product = new Product() { Id = 1, Name = "安慕希", Count = 10, Price = 58.8 };
var data=JsonConvert.SerializeObject(product);

(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签名的方式能够在一定程度上防止信息被篡改和伪造,保障通信的安全

源码地址:https://github.com/13138899620/TokenSign

TOKEN+签名验证的更多相关文章

  1. WebApi安全性 使用TOKEN+签名验证 (秘钥是GUID的,私有的,不是雙方的,并不在网络连接上传输)

    转http://www.cnblogs.com/MR-YY/archive/2016/10/18/5972380.html WebApi安全性 使用TOKEN+签名验证   首先问大家一个问题,你在写 ...

  2. WebApi安全性 使用TOKEN+签名验证

    首先问大家一个问题,你在写开放的API接口时是如何保证数据的安全性的?先来看看有哪些安全性问题在开放的api接口中,我们通过http Post或者Get方式请求服务器的时候,会面临着许多的安全性问题, ...

  3. WebAPI 安全性 使用TOKEN+签名验证(上)

    首先问大家一个问题,你在写开放的API接口时是如何保证数据的安全性的?先来看看有哪些安全性问题在开放的api接口中,我们通过http Post或者Get方式请求服务器的时候,会面临着许多的安全性问题, ...

  4. WebAPI 安全性 使用TOKEN+签名验证(下)

    //根据请求类型拼接参数 NameValueCollection form = HttpContext.Current.Request.QueryString; string data = strin ...

  5. ASP.NET WebApi 基于JWT实现Token签名认证

    一.前言 明人不说暗话,跟着阿笨一起玩WebApi!开发提供数据的WebApi服务,最重要的是数据的安全性.那么对于我们来说,如何确保数据的安全将会是需要思考的问题.在ASP.NET WebServi ...

  6. token令牌

    本文摘自 WebApi安全性 使用TOKEN+签名验证 首先问大家一个问题,你在写开放的API接口时是如何保证数据的安全性的?先来看看有哪些安全性问题在开放的api接口中,我们通过http Post或 ...

  7. 【笔记】vue+springboot前后端分离实现token登录验证和状态保存的简单实现方案

    简单实现 token可用于登录验证和权限管理. 大致步骤分为: 前端登录,post用户名和密码到后端. 后端验证用户名和密码,若通过,生成一个token返回给前端. 前端拿到token用vuex和lo ...

  8. 朱晔和你聊Spring系列S1E10:强大且复杂的Spring Security(含OAuth2三角色+三模式完整例子)

    Spring Security功能多,组件抽象程度高,配置方式多样,导致了Spring Security强大且复杂的特性.Spring Security的学习成本几乎是Spring家族中最高的,Spr ...

  9. WebApi安全性 参数签名校验(结合Axios使用)

    接口参数签名校验,是WebApi接口服务最重要的安全防护手段之一. 结合项目中实际使用情况,介绍下前后端参数签名校验实现方案. 签名校验规则 http请求,有两种传参形式: 1.通过url传参,最常见 ...

随机推荐

  1. C# 之 Excel 导入一列中既有汉字又有数字:数字可以正常导入,汉字导入为空

    今天在做一个Excel导入功能,一切开发就绪,数据可以成功导入.导入后检查数据库发现有一列既有汉字又有数字,数字正常导入,汉字为空.但是前面同样既有汉字又有数字的列可以导入成功. 查看excel 源文 ...

  2. 属性文件Plist

    属性文件: Property List File:简称plist 概念iOS开发中常见的一种文件格式.按照固定格式保存数据. 属性文件和XML文件都属性结构化文件.文件的内容按指定的格式保存数据. 作 ...

  3. SQL Server 2014 AlwaysON

    . 环境准备 虚拟机:ssag-bj-ad-01, ssag-bj-fc-01, ssag-bj-sql-01, ssag-bj-sql-02, ssag-sh-ad-01, ssag-sh-fc-0 ...

  4. 关于JFace带复选框的树

    树的复选框用CheckboxTreeViewer实现.由于其子类ContainerCheckedTreeViewer在没有选择全部子节点时可以自动将父节点设置成灰选,所以实现树的复选框更多的是用Con ...

  5. 并发容器之CopyOnWriteArrayList(转载)

    Copy-On-Write简称COW,是一种用于程序设计中的优化策略.其基本思路是,从一开始大家都在共享同一个内容,当某个人想要修改这个内容的时候,才会真正把内容Copy出去形成一个新的内容然后再改, ...

  6. C#实现插入排序法

    private int[] test_Insertion(int[] list) //插入排序 传入数组 3, 6, 2, 1, 9, 5, 4, 7 { ;i<list.Length;i++) ...

  7. 【linux操作命令】mysql

    1.linux下启动mysql的命令: mysqladmin start /ect/init.d/mysql start (前面为mysql的安装路径) 2.linux下重启mysql的命令: mys ...

  8. JAXB - Calling marshal

    Only a handful of source code lines is required to make a JAXB Marshaller object write a document tr ...

  9. 第十六篇、MBprogressHUD的使用和获取当前的控制器

    简介: 在开发应用的时候,为了更好的增加用户的体验,常常在客户端向服务端请求数据的时候,在适当的地方加上非模态HUD,让用户可以可以知道当前的进度,稍作等待. MBProgressHUD 第三方框架G ...

  10. TCP/IP 教程

    TCP/IP 是因特网的通信协议. 通信协议是对计算机必须遵守的规则的描述,只有遵守这些规则,计算机之间才能进行通信. 浏览器和服务器都在使用 TCP/IP 因特网浏览器和因特网服务器均使用 TCP/ ...