接口的安全性主要围绕Token、Timestamp和Sign三个机制展开设计,保证接口的数据不会被篡改和重复调用。算法
Token受权机制:用户使用用户名密码登陆后服务器给客户端返回一个Token(一般是UUID),并将Token-UserId以键值对的形式存放在缓存服务器中。服务端接收到请求后进行Token验证,若是Token不存在,说明请求无效。Token是客户端访问服务端的凭证。数据库
时间戳超时机制:用户每次请求都带上当前时间的时间戳timestamp,服务端接收到timestamp后跟当前时间进行比对,若是时间差大于必定时间(好比5分钟),则认为该请求失效。时间戳超时机制是防护DOS攻击的有效手段。缓存
签名机制:将 Token 和 时间戳 加上其余请求参数再用MD5或SHA-1算法(可根据状况加点盐)加密,加密后的数据就是本次请求的签名sign,服务端接收到请求后以一样的算法获得签名,并跟当前的签名进行比对,若是不同,说明参数被更改过,直接返回错误标识。签名机制保证了数据不会被篡改。安全
拒绝重复调用(非必须):客户端第一次访问时,将签名sign存放到缓存服务器中,超时时间设定为跟时间戳的超时时间一致,两者时间一致能够保证不管在timestamp限定时间内仍是外 URL都只能访问一次。若是有人使用同一个URL再次访问,若是发现缓存服务器中已经存在了本次签名,则拒绝服务。若是在缓存中的签名失效的状况下,有人使用同一个URL再次访问,则会被时间戳超时机制拦截。这就是为何要求时间戳的超时时间要设定为跟时间戳的超时时间一致。拒绝重复调用机制确保URL被别人截获了也没法使用(如抓取数据)。服务器
一、客户端经过用户名密码登陆服务器并获取Tokenapp
二、客户端生成时间戳timestamp,并将timestamp做为其中一个参数测试
三、客户端将全部的参数,包括Token和timestamp按照本身的算法进行排序加密获得签名sign网站
四、将token、timestamp和sign做为请求时必须携带的参数加在每一个请求的URL后边(http://url/request?token=123×tamp=123&sign=123123123)加密
五、服务端写一个过滤器对token、timestamp和sign进行验证,只有在token有效、timestamp未超时、缓存服务器中不存在sign三种状况同时知足,本次请求才有效url
若是有人劫持了请求,并对请求中的参数进行了修改,签名就没法经过;
若是有人使用已经劫持的URL进行DOS攻击,服务器则会由于缓存服务器中已经存在签名或时间戳超时而拒绝服务,因此DOS攻击也是不可能的;
若是签名算法和用户名密码都暴露了,那真的须要开光了吧。。。。
实际项目中须要根据业务状况做出裁剪,好比能够只使用签名机制就能够保证信息不会被篡改,或者定向提供服务的时候只用Token机制就能够了。
有没有这样的接口,谁均可以调用,谁均可以访问,不受时间空间限制,只要能连上互联网就能调用,毫无安全可言。
查快递,查天气预报,查飞机,火车班次等,都是有公共的接口。
/// <summary> /// 接口对外公开 /// </summary> /// <returns></returns> [HttpGet] [Route("NoSecure")] public HttpResponseMessage NoSecure(int age) { var result = new ResultModel<object>() { ReturnCode = 0, Message = string.Empty, Result = string.Empty }; var dataResult = stulist.Where(T => T.Age == age).ToList(); result.Result = dataResult; return GetHttpResponseMessage(result); }
你写个接口,只想让特定的调用方使用,咱们把调用的人叫到一个小屋子,给他们每人一把钥匙。这把钥匙就是参数加密规则,有了这个规则就能调用。
这有安全问题啊,某个成员不当心丢了钥匙或者被人窃取,掌握钥匙的人是否是也能够来掉用接口了呢?并且能够复制不少钥匙给不明不白的人用。
至关于有人拿到了你的请求连接,若是业务没有对连接惟一性作判断(实际上业务逻辑一般不会把每次请求的加密签名记录下来,因此不会作惟一性判断),就会被重复调用,有必定安全漏洞,怎么破?
/// <summary> /// 接口加密 /// </summary> /// <returns></returns> [HttpGet] [Route("SecureBySign")] public HttpResponseMessage SecureBySign([FromUri]int age, long _timestamp, string appKey, string _sign) { var result = new ResultModel<object>() { ReturnCode = 0, Message = string.Empty, Result = string.Empty }; #region 校验签名是否合法 var param = new SortedDictionary<string, string>(new AsciiComparer()); param.Add("age", age.ToString()); param.Add("appKey", appKey); param.Add("_timestamp", _timestamp.ToString()); string currentSign = SignHelper.GetSign(param, appKey); if (_sign != currentSign) { result.ReturnCode = -2; result.Message = "签名不合法"; return GetHttpResponseMessage(result); } #endregion var dataResult = stulist.Where(T => T.Age == age).ToList(); result.Result = dataResult; return GetHttpResponseMessage(result); }
继上一步,你天天给他们换一把钥匙。和往常同样,小偷煞费苦心,准备在一个月黑风高的夜晚动手。拿出钥匙,捣鼓了半天也没法开启你的神圣之门,由于小偷不知道你每天都在换新钥匙。
小偷不服,在一次得到钥匙以后,当天就动手了,由于他知道他手里的钥匙在次日你更换钥匙后就失效了。
/// <summary> /// 接口加密并根据时间戳判断有效性 /// </summary> /// <returns></returns> [HttpGet] [Route("SecureBySign/Expired")] public HttpResponseMessage SecureBySign_Expired([FromUri]int age, long _timestamp, string appKey, string _sign) { var result = new ResultModel<object>() { ReturnCode = 0, Message = string.Empty, Result = string.Empty }; #region 判断请求是否过时---假设过时时间是20秒 DateTime requestTime = GetDateTimeByTicks(_timestamp); if (requestTime.AddSeconds(20) < DateTime.Now) { result.ReturnCode = -1; result.Message = "接口过时"; return GetHttpResponseMessage(result); } #endregion #region 校验签名是否合法 var param = new SortedDictionary<string, string>(new AsciiComparer()); param.Add("age", age.ToString()); param.Add("appKey", appKey); param.Add("_timestamp", _timestamp.ToString()); string currentSign = SignHelper.GetSign(param, appKey); if (_sign != currentSign) { result.ReturnCode = -2; result.Message = "签名不合法"; return GetHttpResponseMessage(result); } #endregion var dataResult = stulist.Where(T => T.Age == age).ToList(); result.Result = dataResult; return GetHttpResponseMessage(result); }
继上一步,咋办呢?你打算下血本,给每一个人配一把钥匙的基础上,再给每一个人发个暗号,即便钥匙被小偷弄去了,小偷没有暗号,任然没法如愿,并且这样很容易定位是谁的暗号泄漏问题,找到问题根源
但这个钥匙还有可能被小偷搞到。
代码以下:
/// <summary> /// 接口加密并根据时间戳判断有效性并且带着私有key校验 /// </summary> /// <returns></returns> [HttpGet] [Route("SecureBySign/Expired/KeySecret")] public HttpResponseMessage SecureBySign_Expired_KeySecret([FromUri]int age, long _timestamp, string appKey, string _sign) { //key集合,这里随便弄两个测试数据 //若是调用方比较多,须要审核受权,根据必定的规则生成key把这些数据存放在数据库中,若是功能扩展开来,能够针对不一样的调用方作不一样的功能权限管理 //在调用接口时动态从库里取,每一个调用方在调用时带上他的key,调用方通常把本身的key放到网站配置中 Dictionary<string, string> keySecretDic = new Dictionary<string, string>(); keySecretDic.Add("key_zhangsan", "D9U7YY5D7FF2748AED89E90HJ88881E6");//张三的key, keySecretDic.Add("key_lisi", "I9O6ZZ3D7FF2748AED89E90ZB7732M9");//李四的key var result = new ResultModel<object>() { ReturnCode = 0, Message = string.Empty, Result = string.Empty }; #region 判断请求是否过时---假设过时时间是20秒 DateTime requestTime = GetDateTimeByTicks(_timestamp); if (requestTime.AddSeconds(20) < DateTime.Now) { result.ReturnCode = -1; result.Message = "接口过时"; return GetHttpResponseMessage(result); } #endregion #region 根据appkey获取key值 string secret = keySecretDic.Where(T => T.Key == appKey).FirstOrDefault().Value; #endregion #region 校验签名是否合法 var param = new SortedDictionary<string, string>(new AsciiComparer()); param.Add("age", age.ToString()); param.Add("appKey", appKey); param.Add("appSecret", secret);//把secret加入进行加密 param.Add("_timestamp", _timestamp.ToString()); string currentSign = SignHelper.GetSign(param, appKey); if (_sign != currentSign) { result.ReturnCode = -2; result.Message = "签名不合法"; return GetHttpResponseMessage(result); } #endregion var dataResult = stulist.Where(T => T.Age == age).ToList(); result.Result = dataResult; return GetHttpResponseMessage(result); }
继上一步,咱们给传输机制改成Https,这下小偷完全懵逼了。
那么问题来了,Https咋玩儿呢?