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 = ;
- //判断参数是否合法
- 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();
- 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 == )
- 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(, queryStr.Length - ));
- }
post请求:将请求的参数对象序列化为json格式字符串
- Product product = new Product() { Id = , Name = "安慕希", Count = , 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 = ;
- 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 = ;
- double ts2 = (DateTime.UtcNow - new DateTime(, , , , , , )).TotalMilliseconds;
- bool timespanvalidate = double.TryParse(timestamp, out ts1);
- double ts = ts2 - ts1;
- bool falg = ts > int.Parse(WebSettingsConfig.UrlExpireTime) * ;
- 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 = ; 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.不合法的请求就会被识别为请求参数已被修改
合法的请求则会返回对应的商品信息
3.判断签名是否成功,第一次请求签名参数signature和服务器端计算result完全相同, 然后当把请求参数中count的数量从10改成100之后服务器端计算的result和请求签名参数signature不同,所以请求不合法,是非法请求,同理如果其他任何参数被修改最后计算的结果都会和签名参数不同,请求同样识别为不合法请求
总结:
通过上面的案例,我们可以看出,安全的关键在于参与签名的TOKEN,整个过程中TOKEN是不参与通信的,所以只要保证TOKEN不泄露,请求就不会被伪造。
然后我们通过timestamp时间戳用来验证请求是否过期,这样就算被人拿走完整的请求链接也是无效的。
Sign签名的方式能够在一定程度上防止信息被篡改和伪造,保障通信的安全
源码地址:https://github.com/13138899620/TokenSign
token令牌的更多相关文章
- .NET WebAPI 用ActionFilterAttribute实现token令牌验证与对Action的权限控制
项目背景是一个社区类的APP(求轻吐...),博主主要负责后台业务及接口.以前没玩过webAPI,但是领导要求必须用这个(具体原因鬼知道),只好硬着头皮上了. 最近刚做完权限这一块,分享出来给大家.欢 ...
- WebAPI 用ActionFilterAttribute实现token令牌验证与对Action的权限控制
.NET WebAPI 用ActionFilterAttribute实现token令牌验证与对Action的权限控制 项目背景是一个社区类的APP(求轻吐...),博主主要负责后台业务及接口.以前没玩 ...
- Token令牌管理权限
什么是token HTTP是一种无状态的协议,也就是HTTP没法保存客户端的信息,没办法区分每次请求的不同. Token是服务器生成的一串字符,作为客户端请求的令牌.当第一次登陆后,服务器会分发Ton ...
- 微信小程序--消息推送配置Token令牌错误校验失败如何解决
微信开放第三方API接口, 申请地址: https://mp.weixin.qq.com/advanced/advanced?action=interface&t=advanced/inter ...
- .net core 基于Jwt实现Token令牌
Startup类ConfigureServices中 services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJw ...
- APP开放接口API安全性——Token令牌Sign签名的设计与实现
在APP开放接口API的设计中,避免不了的就是安全性问题. 一.https协议 对于一些敏感的API接口,需要使用https协议.https是在http超文本传输协议加入SSL层,它在网络间通信是加密 ...
- 在ASP.NET Web API 2中使用Owin基于Token令牌的身份验证
基于令牌的身份验证 基于令牌的身份验证主要区别于以前常用的常用的基于cookie的身份验证,基于cookie的身份验证在B/S架构中使用比较多,但是在Web Api中因其特殊性,基于cookie的身份 ...
- 接口认证:Bearer Token(Token 令牌)
因为HTTP协议是开放的,可以任人调用.所以,如果接口不希望被随意调用,就需要做访问权限的控制,认证是好的用户,才允许调用API. 目前主流的访问权限控制/认证模式有以下几种: 1)Bearer To ...
- java:struts框架3(自定义拦截器,token令牌,文件上传和下载(单/多))
1.自定义拦截器: struts.xml: <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE ...
- 一、Core授权-2 之.net core 基于Jwt实现Token令牌
一.Startup类配置 ConfigureServices中 //添加jwt验证: services.AddAuthentication(JwtBearerDefaults.Authenticati ...
随机推荐
- HTML常用标签及属性
标签格式 格式: 双边:<标签名 属性1="值1" 属性2='值2' 属性3=值3>内容</标签名> 单边:<标签名 属性1="值1&quo ...
- [VUE ERROR] Error in render: "TypeError: Cannot create property 'header' on boolean 'true'"
项目基于ElemnetUi进行的开发,在引入第三方扩展库 vue-element-extends 之后使用它的表格组件报了这个错 解决方案: 1. 删除项目中的 node_modules 2. 删除 ...
- git 查看/修改用户名、密码
用户名和邮箱地址的作用 用户名和邮箱地址是本地git客户端的一个变量,不随git库而改变. 每次commit都会用用户名和邮箱纪录. github的contributions统计就是按邮箱来统计的. ...
- MySQL主从及主主环境部署
主从同步 主机环境 mysql的安装可以参考:https://www.cnblogs.com/brianzhu/p/8575243.htmlCentos7版本master:192.168.192.12 ...
- 使用volley上传多张图片,一个参数对应多张图片,转载
https://my.oschina.net/u/1177694/blog/491834 原帖地址 而如果使用volley的话,因为请求数据那些都很简便,但遇到上传文件就麻烦那可不好,同时使用多个网络 ...
- python:序列化与数据持久化
数据持久化的方式有: 1.普通文件无格式写入:将数据直接写入到文件中 2.普通序列化写入:json,pickle 3.DBM方式:shelve,dbm 相关内容: json pickle shelve ...
- InnoDB中锁的模式,锁的查看,算法
InnoDB中锁的模式 Ⅰ.总览 S行级共享锁lock in share mode X行级排它锁增删改 IS意向共享锁 IX意向排他锁 AI自增锁 Ⅱ.锁之间的兼容性 兼 X IX S IS X ...
- WFE和WFI的区别
1. 概念: WFI(Wait for interrupt)和WFE(Wait for event)是两个让ARM核进入low-power standby模式的指令,由ARM architecture ...
- if条件简单语法
if语句是实际工作中最重要最常用的语句. if条件语法: 单分支结构 if [ 条件 ] then 指令 fi 或 if [ 条件 ]:then 指令 fi if 单分支条件中文编程形象语法: 如果 ...
- PHP 截取字符串乱码的解决方案
今天遇到一个坑,左右调试坑的我一脸懵逼,当我们对一条字符串进行截取的时候,通常第一个想到的就是substr()函数了,但是如果是中文+数字的字符串的话,这时候使用substr进行截取就会出现乱码的问题 ...