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+签名认证的主要原理是: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()); //当前请求用户StaffIdrequest.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
WebApi安全性 使用TOKEN+签名验证相关推荐
- WebApi后端框架Token身份认证,Api接口Token验证
令牌概述(Token) 在以用户账号体系作为安全认证的信息系统中,对用户身份的鉴定是非常重要的事情. 令牌机制是软件系统安全体系中非常重要的部分,在计算机身份认证中是令牌的意思,一般作为邀请.登录以及 ...
- webapi中使用token验证(JWT验证)
转自:https://www.cnblogs.com/ye-hcj/articles/8151385.html 本文介绍如何在webapi中使用JWT验证 准备 安装JWT安装包 System.Ide ...
- 私人工具集7——webapi中的Token时效性验证
子曰:工欲善其事,必先利其器 github:https://github.com/redAntCpp/CSharpTools 紧接上篇,上篇只是实现了token的生成以及解析,顺便提到了验证机制.但是 ...
- ASP.NET WebApi 基于分布式Session方式实现Token签名认证
一.课程介绍 明人不说暗话,跟着阿笨一起学玩WebApi!开发提供数据的WebApi服务,最重要的是数据的安全性.那么对于我们来说,如何确保数据的安全将会是需要思考的问题.在ASP.NETWebSer ...
- 使用 做签名的post_ASP.NET WebApi 基于分布式Session方式实现Token签名认证
一.课程介绍 明人不说暗话,跟着阿笨一起玩WebApi!开发提供数据的WebApi服务,最重要的是数据的安全性.那么对于我们来说,如何确保数据的安全将会是需要思考的问题.在ASP.NETWebServ ...
- WebApi的安全性解决方案
一.前言 WebApi的小白想要了解一些关于WebApi安全性相关的问题,本篇文章是整理一些关于WebApi安全.权限认证的文章. 二.内容正文 2.1 不进行验证 客户端调用:http:// ...
- [JAVA EE]session 和 token 机制
HTTP 协议是无状态的. 即服务器对于客户端每次发送的请求都认为它是一个新的请求. 本次请求和上次请求无法判断是不是同一个客户端操作的. 随着 Web 应用的发展,如在线购物网站,需要登录的网站等, ...
- jwttoken解码_使用 JSON WEB TOKEN (jwt) 验证
一.什么JSON Web Tokens? JSON Web Tokens是一种开放的行业标准 RFC 7519方法,用于在双方之间安全地表示索赔. JWT.IO允许您解码,验证和生成JWT.其中.J ...
- 手动生成token_手动设计简单的Token验证
简述一下项目中手写的Token验证服务设计过程 PART A 设计校验的哈希算法 这里直接展示整个项目中用到的算法库,其中涉及位运算的可不管 直接应用到的方法是hash(str) 大概流程如下 1.构 ...
最新文章
- ie和firefox操作table对象的异同
- ffmpeg Windows Error 0xe06d7363
- MyEclipse 16(汉化版)安装教程
- linux的Ubuntu
- Java集合面试问题
- 内核模块编程之入门(二)—必备知识
- 今日恐慌与贪婪指数为77 贪婪程度有所上升
- python怎么重新开始_Python-重新开始游戏
- 2d unity 多物体 射线_24小时入门Unity游戏开发系列教程
- jq js json 转字符串_JSON字符串转换成JSON对象的JS代码
- winform适应不同的分辨率_C# Winform的自适应分辨率的类-阿里云开发者社区
- 找网络高手联系方式_怎么才能联系到网络高手(找网络高手联系方式)
- 使用Java解析Json
- 中国微流体元件行业市场供需与战略研究报告
- Source Insight 4.0 选中高亮
- mysql table plugin_MySQL 启动报错Table 'mysql.plugin' doesn't exis(转载)
- 玖月:如果真的能够穿越回到过去,我可能只会给自己一个微笑
- css中a标签超链接在新窗口中打开以及超链接去除/添加下划线
- oracle神谕卡鉴赏,关于神谕卡的科普和答疑汇总(1)
- 【STM32】窗口看门狗程序
热门文章
- C语言检查列表是否是回文的算法(附完整源码)
- OpenGL shader uniform的实例
- STL的tuple集合对象
- QT的QMultiSampleAntiAliasing类的使用
- C语言数据类型本质分析
- 经典C语言程序100例之五
- 「ProtocolBuffers2」ProtocolBuffers2 Python简易入门
- 13.配置 influxDB 鉴权及 HTTP API 写数据的方法
- hbase数据库介绍,HBASE的特点,表结构逻辑视图,Row Key,列族,时间戳,Cell
- 03_TortoiseGit冲突和补丁演示,补丁冲突