WebAPI 安全性 使用TOKEN+签名验证(上)
首先问大家一个问题,你在写开放的API接口时是如何保证数据的安全性的?先来看看有哪些安全性问题在开放的api接口中,我们通过http Post或者Get方式请求服务器的时候,会面临着许多的安全性问题,例如:
请求来源(身份)是否合法?
请求参数被篡改?
请求的唯一性(不可复制),防止请求被恶意攻击
为了保证数据在通信时的安全性,我们可以采用TOKEN+参数签名的方式来进行相关验证。
比如说我们客户端需要查询产品信息这个操作来进行分析,客户端点击查询按钮==》调用服务器端api进行查询==》服务器端返回查询结果
一、不进行验证的方式
api查询接口:
客户端调用:http://api.XXX.com/getproduct?id=value1
如上,这种方式简单粗暴,在浏览器直接输入"http://api.XXX.com/getproduct?id=value1",即可获取产品列表信息了,但是这样的方式会存在很严重的安全性问题,没有进行任何的验证,大家都可以通过这个方法获取到产品列表,导致产品信息泄露。
那么,如何验证调用者身份呢?如何防止参数被篡改呢?如何保证请求的唯一性? 如何保证请求的唯一性,防止请求被恶意攻击呢?
二、使用TOKEN+签名认证 保证请求安全性
token+签名认证的主要原理是:
做一个认证服务,提供一个认证的webapi,用户先访问它获取对应的token
用户拿着相应的token以及请求的参数和服务器端提供的签名算法计算出签名后再去访问指定的api
服务器端每次接收到请求就获取对应用户的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();
}
WebAPI 安全性 使用TOKEN+签名验证(上)的更多相关文章
- WebApi安全性 使用TOKEN+签名验证 (秘钥是GUID的,私有的,不是雙方的,并不在网络连接上传输)
转http://www.cnblogs.com/MR-YY/archive/2016/10/18/5972380.html WebApi安全性 使用TOKEN+签名验证 首先问大家一个问题,你在写 ...
- WebApi安全性 使用TOKEN+签名验证
首先问大家一个问题,你在写开放的API接口时是如何保证数据的安全性的?先来看看有哪些安全性问题在开放的api接口中,我们通过http Post或者Get方式请求服务器的时候,会面临着许多的安全性问题, ...
- WebAPI 安全性 使用TOKEN+签名验证(下)
//根据请求类型拼接参数 NameValueCollection form = HttpContext.Current.Request.QueryString; string data = strin ...
- WebApi安全性 参数签名校验(结合Axios使用)
接口参数签名校验,是WebApi接口服务最重要的安全防护手段之一. 结合项目中实际使用情况,介绍下前后端参数签名校验实现方案. 签名校验规则 http请求,有两种传参形式: 1.通过url传参,最常见 ...
- TOKEN+签名验证
TOKEN+签名验证 首先问大家一个问题,你在写开放的API接口时是如何保证数据的安全性的?先来看看有哪些安全性问题在开放的api接口中,我们通过http Post或者Get方式请求服务器的时候,会面 ...
- 使用localResizeIMG3+WebAPI实现手机端图片上传
前言 惯例~惯例~昨天发表的使用OWIN作为WebAPI的宿主..嗯..有很多人问..是不是缺少了什么 - - 好吧,如果你要把OWIN寄宿在其他的地方...代码如下: namespace Conso ...
- webapi中使用token验证(JWT验证)
本文介绍如何在webapi中使用JWT验证 准备 安装JWT安装包 System.IdentityModel.Tokens.Jwt 你的前端api登录请求的方法,参考 axios.get(" ...
- App开放接口API安全性之Token签名Sign的设计与实现
前言 在app开放接口api的设计中,避免不了的就是安全性问题,因为大多数接口涉及到用户的个人信息以及一些敏感的数据,所以对这些接口需要进行身份的认证,那么这就需要用户提供一些信息,比如用户名密码等, ...
- ASP.NET WebAPi之断点续传下载(上)
前言 之前一直感觉断点续传比较神秘,于是想去一探究竟,不知从何入手,以为就写写逻辑就行,结果搜索一番,还得了解相关http协议知识,又花了许久功夫去看http协议中有关断点续传知识,有时候发觉东西只有 ...
随机推荐
- hdu1114 Piggy-Bank ——完全背包
link:http://acm.hdu.edu.cn/showproblem.php?pid=1114 只不过求得是最小值.没什么可说的,连我都会做……o(╯□╰)o /* ID: zypz4571 ...
- sgu546 Ternary Password
题目链接:http://acm.sgu.ru/problem.php?contest=0&problem=546 这题还好,1Y,考虑情况周全,就没问题了,还好提交之前把想到的情况都测试了一遍 ...
- 关于sql where id in 转换成数据类型 int 时失败(转)
有执行sql条件语句where id in(@参数)的时候,如果处理不当,就会出现问题:如下面这个存储过程: alter proc Web_gettwtwgoldgameserverGoldSell@ ...
- leetcode 153. Find Minimum in Rotated Sorted Array --------- java
Suppose a sorted array is rotated at some pivot unknown to you beforehand. (i.e., 0 1 2 4 5 6 7 migh ...
- Windows && Linux 双系统
使用软件 EasyBCD 添加新条目--->NeoGrub--->安装--->配置 在弹出的文本中输入如下东西: title Install Backbox root (hd0,0) ...
- flash全屏输入模式
params.allowscriptaccess = "sameDomain"; params.allowfullscreen = "true"; params ...
- 【转】非常适用的Sourceinsight插件,提高效率事半功倍
原文网址:http://www.cnblogs.com/wangqiguo/p/3713211.html 一直使用sourceinsight编辑C/C++代码,sourceinsight是一个非常好用 ...
- 安全-分析深圳电信的新型HTTP劫持方式
ISP的劫持手段真是花样百出,从以前的DNS(污染)劫持到后来的共享检测,无不通过劫持正常的请求来达到他们的目的. 之前分析过通过劫持HTTP会话,插入iframe来检测用户后端有无共享行为,但后来移 ...
- linux服务之git
http://www.cnblogs.com/fnng/archive/2011/08/25/2153807.html http://www.cnblogs.com/sunada2005/archiv ...
- 【转】php curl 伪造IP来源的实例代码
curl发出请求的文件fake_ip.php: 代码 复制代码 代码如下: <?php $ch = curl_init(); $url = "http://localhost/targ ...