`

TOKEN+签名验证

    博客分类:
  • C#
 
阅读更多

首先问大家一个问题,你在写开放的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

 

分享到:
评论

相关推荐

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

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

    WebApi 使用TOKEN+签名验证

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

    WebApi Token+ 数字签名认证源码

    代码包括客户端和服务器,前端jquery,后端C#代码。绕过验证与拦截器验证token和数字证书

    WebApi_Token

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

    koa+jwt实现token验证与刷新功能

    该信息可以被验证和信任,因为它是数字签名的。 本文只讲Koa2 + jwt的使用,不了解JWT的话请到这里)进行了解。 koa环境 要使用koa2+jwt需要先有个koa的空环境,搭环境比较麻烦,我直接使用koa起手式,这是我使用koa+...

    verify-apple-id-token:验证服务器端的Apple ID令牌

    使用服务器的公钥验证JWS E256签名 验证随机数以进行身份​​验证 验证iss字段包含 验证aud字段是开发人员的client_id 验证时间早于令牌的exp值 安装 npm install verify-apple-id-token 用法 打字稿 import ...

    onenet对接token的python及QuecPython算法

    对接onenet需要token,资料中是python的算法实现,同时还包含移远模块的QuecPython算法,程序经过验证,输入相应参数可以得到正确token,方便开发者嵌入

    cognito-express:通过验证Amazon Cognito生成的AccessToken或IDToken的JWT签名来验证Node应用程序上的API请求

    cognito-express通过验证由Amazon Cognito生成的AccessToken或IDToken的JWT签名,对Node.js应用程序(在服务器上运行或在AWS Lambda函数中运行)上的API请求进行身份验证。 动机 通过此模块,您可以通过验证...

    JWT 生成Token实战验证.pdf

    因为数字签名的存在,这些信息是可信的,JT可以使用HMAC算法或者是RSA的公私秘钥对进行签名。简洁( compact):可以通过URL,POST参数或者在 Http header发送,因为数据量小,传输速度也很快自包含(Sel|f- ...

    asp微信 token 换取 微信分享 微信支付代码

    asp代码是自己编写的,一直在用,公众号申请一下APPid,asp微信 token 换取 微信分享 微信支付代码,asp微信 token 换取 微信分享 微信支付代码,

    firebase_id_token:Ruby宝石,用于验证Firebase ID令牌的签名

    Ruby Firebase ID令牌验证程序(预发行版) 用来验证Firebase ID令牌签名的Ruby宝石。 它使用Redis来存储Google的x509证书并管理其过期时间,因此您无需在每次执行中都请求Google的API,并且可以像从内存中读取数据...

    New Paper 一键注入卡密验证.rar

    2.卡密验证,远程公告弹窗,分享弹窗,应用更新,云端签名验证(二次签名闪退,防破解),应用检测(检测到手机上下载了某个apk执行某些任务) 3.一键注入VPN抓包检测,虚拟机检测,Xposed检测,防止截屏 5.多重md5,...

    微信公众平台接口使用-连接验证(asp.net)

    //TOKEN签名 protected void Page_Load(object sender, EventArgs e) { string postStr = ""; if (Request.HttpMethod.ToLower() == "post")//判断传输方法是否为post { Stream s = System.Web.HttpContext....

    ASP.NET webapi方式客户端提交签名数据到服务端返回JSON数据交互示例源码

    WebAPI:client客户端post提交签名数据/apiServer服务端返回json数据交互 客户端: Web/ApiClient/queryToken.aspx API服务端:Web/Api/ApiServe/queryToken.aspx 客户端(Web/ApiClient/queryToken.aspx )以post方式...

    SpringBoot集成JWT实现token验证的流程

    Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).这篇文章主要介绍了SpringBoot集成JWT实现token验证,需要的朋友可以参考下

    Unity3d www Http 请求 Headers 验证

    Unity3d www Http 请求 Headers 验证 文档地址:https://blog.csdn.net/nicepainkiller/article/details/75008516

    SpringAuth:使用JWT身份验证进行Spring授权的示例项目

    SpringAuth:使用JWT身份验证进行Spring授权的示例项目

    google_id_token:使用Google OpenID Connect令牌进行身份验证

    使用Google OpenID Connect令牌进行身份验证本节介绍根据安全边界进行身份验证,这要求客户端提供有效的OpenID Connect令牌。 这些安全范围不会保护Google API,但会保护您在某些Google Cloud产品之后部署的服务。 ...

    thinkphp框架使用JWTtoken的方法详解

    主要介绍了thinkphp框架使用JWTtoken的方法,结合实例形式分析了JWTtoken的功能、原理及thinkPHP使用JWTtoken实现签名验证的相关操作技巧,需要的朋友可以参考下

    WEB API 接口签名验证入门与实战课程

    通过对API接口编写与请求过程的分析认识到接口请求中的安全问题,并利用签名验证解决这些问题 这些 API数据传输各种安全问题包括: 1、如何接口请求这的合法性 2、如何保证登陆的安全性 3、如何保证请求的参数不被...

Global site tag (gtag.js) - Google Analytics