WebAPI 安全性 使用TOKEN+签名验证(上)

  web培训首先问大家一个问题,你在写开放的API接口时是如何保证数据的安全性的?先来看看有哪些安全性问题在开放的api接口中,我们通过http Post或者Get方式请求服务器的时候,会面临着许多的安全性问题,例如:

  请求来源(身份)是否合法?

  请求参数被篡改?

  请求的唯一性(不可复制),防止请求被恶意攻击

  为了保证数据在通信时的安全性,我们可以采用TOKEN+参数签名的方式来进行相关验证。

  比如说我们客户端需要查询产品信息这个操作来进行分析,客户端点击查询按钮==》调用服务器端api进行查询==》服务器端返回查询结果

  一、不进行验证的方式

  api查询接口:

  客户端调用:http://api.XXX.com/getproduct?id=value1

  如上,这种方式简单粗暴,在浏览器直接输入"http://api.XXX.com/getproduct?id=value1",即可获取产品列表信息了,但是这样的方式会存在很严重的安全性问题,没有进行任何的验证,大家都可以通过这个方法获取到产品列表,导致产品信息泄露。

  那么,如何验证调用者身份呢?如何防止参数被篡改呢?如何保证请求的唯一性? 如何保证请求的唯一性,防止请求被恶意攻击呢?

  二、使用TOKEN+签名认证 保证请求安全性

  token+签名认证的主要原理是:

  做一个认证服务,提供一个认证的webapi,用户先访问它获取对应的token

  用户拿着相应的token以及请求的参数和服务器端提供的签名算法计算出签名后再去访问指定的api

  服务器端每次接收到请求就获取对应用户的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 GetQueryString(Dictionary parames)

  {

  // 第一步:把字典按Key的字母顺序排序

  IDictionary sortedParams = new SortedDictionary(parames);

  IEnumerator> dem = sortedParams.GetEnumerator();

  // 第二步:把所有参数名和参数值串在一起

  StringBuilder query = new StringBuilder(""); //签名字符串

  StringBuilder queryStr = new StringBuilder(""); //url参数

  if (parames == null || parames.Count == 0)

  return new Tuple("","");

  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(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();

  }

时间: 2024-10-10 07:56:32

WebAPI 安全性 使用TOKEN+签名验证(上)的相关文章

WebAPI 安全性 使用TOKEN+签名验证(下)

web教程:WebAPI 安全性 使用TOKEN+签名验证(下) //根据请求类型拼接参数 NameValueCollection form = HttpContext.Current.Request.QueryString; string data = string.Empty; switch (method) { case "POST": Stream stream = HttpContext.Current.Request.InputStream; string response

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

首先问大家一个问题,你在写开放的API接口时是如何保证数据的安全性的?先来看看有哪些安全性问题在开放的api接口中,我们通过http Post或者Get方式请求服务器的时候,会面临着许多的安全性问题,例如: 请求来源(身份)是否合法? 请求参数被篡改? 请求的唯一性(不可复制),防止请求被恶意攻击 为了保证数据在通信时的安全性,我们可以采用TOKEN+参数签名的方式来进行相关验证. 比如说我们客户端需要查询产品信息这个操作来进行分析,客户端点击查询按钮==>调用服务器端api进行查询==>服务

TOKEN+签名验证

TOKEN+签名验证 首先问大家一个问题,你在写开放的API接口时是如何保证数据的安全性的?先来看看有哪些安全性问题在开放的api接口中,我们通过http Post或者Get方式请求服务器的时候,会面临着许多的安全性问题,例如: 请求来源(身份)是否合法? 请求参数被篡改? 请求的唯一性(不可复制),防止请求被恶意攻击 为了保证数据在通信时的安全性,我们可以采用TOKEN+参数签名的方式来进行相关验证. 比如说我们客户端需要查询产品信息这个操作来进行分析,客户端点击查询按钮==>调用服务器端ap

webapi中使用token验证(JWT验证)

本文介绍如何在webapi中使用JWT验证 准备 安装JWT安装包 System.IdentityModel.Tokens.Jwt 你的前端api登录请求的方法,参考 axios.get("api/token?username=cuong&password=1").then(function (res) { // 返回一个token /* token示例如下 "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZ

h5+带token附件上传:plus.uploader.createUpload

//mainUrls = 需要上传的urlvar task = plus.uploader.createUpload(mainUrls, {method: "POST",//<注意>这里不能像ajax一样把token放在这里.//beforeSend: function(request) {// request.setRequestHeader(tokenHeader, appToken);//},//<请求类型>headers: {"Content-

webapi跨域,服务器上使用session

最近的项目,要求前后端分离,手机客户端使用的是微信小程序,服务器接口,使用webapi接口分离,pc端后台管理也分离. 这里要说的是,后台pc管理端和服务器的API之间,使用session验证是否登录:后台客户端管理使用的是vue全家桶+axios 1.首先需要开启服务器端的session,需要在Global.asax文件中添加一下代码: public override void Init() { this.AuthenticateRequest += WebApiApplication_Aut

如何调用基于TOKEN签名验证ASP.NET Web API

1.首先获得Token 2.通过Token调用

WebApi实现单个文件的上传下载

上传和下载是很常用的功能了,只有当用到的时候才发现不会写...,经过一番百度.筛选.整理修改后,实现了功能,下面简单的记录下实现方法. 一.上传功能 1.前端代码 上传文件 <input type="file" id="file" /> <input type="button" id="upload" value="上传文件" /> <script> //上传 $(&qu

aps.net mvc webapi 实现文件或图片上传

前几天看到网上有很多复杂的实现方式,觉得没必要,所以就写个简单的实现. 一:首先来看看Api Controller里面的代码: HttpContext.Current.Request.Files  这是一个文件集合对象,你客户端上载的所有文件都在这个集合当中 图中提供了2种方式获取单个文件对象,你可以按需使用,单个文件可以直接用下标,多个文件用name,例如: HttpPostedFile file =HttpContext.Current.Request.Files[0]; 图中HttpPos