`

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+签名验证"是一种常见的安全策略,它结合了令牌(Token)验证和签名机制,以确保只有授权的客户端能够访问服务。下面将详细介绍这两种方法及其在实际应用中的实现。 1. **令牌(Token)验证**: ...

    WebApi Token+ 数字签名认证源码

    6. **数字签名**:在Token验证过程中,数字签名用于确认Token的完整性和来源。服务器使用私钥签名,客户端使用服务器的公钥验证。这确保了即使Token被截获,攻击者也无法伪造有效的Token。 总结,WebApi Token+ ...

    Api接口TOKEN+签名安全.zip

    "Api接口TOKEN+签名安全.zip"这个压缩包文件很显然是为了探讨如何在Web API中实现安全的鉴权和验证机制。在这个场景下,`TOKEN`和`签名`是两个关键概念,它们在保护API免受未授权访问、防止数据篡改等方面起着重要...

    WebApi+Token+数字签名.zip

    Web API是Microsoft为构建RESTful服务提供的一种框架,它基于ASP.NET,用于创建可以处理...通过学习和实践C# WebAPI中的Token和数字签名,开发者能够更好地理解身份验证和数据安全的实现方法,提升Web应用的安全性。

    Redis+接口+token+Sign+时间戳 Demo

    总结起来,"Redis+接口+token+Sign+时间戳 Demo"展示了如何利用Redis来提升接口安全性和性能,通过token验证用户,Sign保证数据完整,时间戳防止重放攻击,并且可能包含了一个用于生成和验证验证码的服务。...

    Token认证签名加密

    在IT行业中,Token认证签名加密是一种常见的安全机制,主要用于确保数据传输的安全性和验证请求的合法性。这个主题涉及了几个核心概念,包括Token、加密和签名,这些都是构建安全网络服务的关键要素。 1. **Token**...

    WebApi_Token

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

    C#WEB用户令牌TOKEN验证防止HTTPGETPOST等提交

    1. **C#中的Token验证**: - ASP.NET Core Identity:这是.NET框架提供的一个身份管理库,支持用户注册、登录、密码重置等功能,并可以生成基于Cookie的认证令牌。 - JWT:JWT是一种轻量级的身份验证协议,它允许...

    JWT Token生成及验证

    这个信息可以被验证和信任,因为它是数字签名的。JWT在身份验证和授权场景中广泛应用,尤其是在微服务架构和API安全领域。 ### JWT Token的基本构成 JWT由三部分组成,每部分由点(.)分隔: 1. **头部(Header)...

    c#生成token验证4

    验证JWT主要涉及到解析Token、检查签名以及验证Token的有效性。以下是一个基本的验证示例: 1. 解析JWT: ```csharp var handler = new JwtSecurityTokenHandler(); var jwtToken = handler.ReadJwtToken...

    JWT Token生成及验证(源码)

    这个"JWT Token生成及验证(源码)"的压缩包文件可能包含了一个示例项目,用于演示如何生成和验证JWT令牌。让我们深入探讨JWT的工作原理以及相关的关键知识点。 1. JWT结构: JWT令牌由三部分组成,用点(.)分隔:...

    JWT 生成Token及验证

    验证JWT的Token需要执行相反的操作,即首先对JWT的签名部分进行解码和验证,然后验证Header信息,以及检查Payload中的声明是否符合预期。在实际应用中,可以使用各种语言提供的JWT库来简化这一过程。 在使用JWT进行...

    java token验证和注解方式放行

    这通常涉及到解码Token,检查签名,以及验证Token是否过期等步骤。这个过程可能需要与Token颁发者(如身份验证服务器)进行交互,以确认Token的真实性。 其次,注解方式的放行策略是一种灵活的权限控制手段。在Java...

    API接签名验证

    Access Token是一种短期的、一次性使用的凭证,客户端需要在请求中携带此Token,服务端会验证Token的有效性后再进行签名验证,这样即使签名被截获,也无法用于其他请求。 通过以上步骤,我们可以看到API接口签名...

    springboot+redis+token保持登录

    7. **安全性考虑**: 除了基本的签名验证,还需要防止Token被篡改或重放攻击。可以使用HTTPS进行通信,保证数据传输的安全。同时,服务器端应限制同一时间内一个Token只能在一个设备上有效,防止会话劫持。 通过以上...

    Token验证的代码

    JWT是一个开放标准,它定义了一个紧凑的、自包含的方式来安全地传输信息作为JSON对象,这个信息可以被验证和信任,因为它是数字签名的。OAuth2则主要用于授权,允许第三方应用代表用户获取特定资源的访问权限。 在...

Global site tag (gtag.js) - Google Analytics