欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

asp.net mvc webapi 实用的接口加密方法示例

程序员文章站 2022-03-27 08:15:26
在很多项目中,因为webapi是对外开放的,这个时候,我们就要得考虑接口交换数据的安全性。 安全机制也比较多,如andriod与webapi 交换数据的时候,可以走双向证...

在很多项目中,因为webapi是对外开放的,这个时候,我们就要得考虑接口交换数据的安全性。

安全机制也比较多,如andriod与webapi 交换数据的时候,可以走双向证书方法,但是开发成本比较大,

今天我们不打算介绍这方面的知识,我们说说一个较简单也较常见的安全交换机制

在这里要提醒读者,目前所有的加密机制都不是绝对的安全

我们的目标是,任何用户或者软件获取到我们的webapi接口url后用来再次访问该地址都是无效的!

达到这种目标的话,我们必须要在url中增加一个时间戳,但是仅仅如此还是不够,用户可以修改我们的时间戳!

因此我们可以对时间戳 进行md5加密,但是这样依然不够,用户可以直接对我们的时间戳md5的哦,因些需要引入一个绝对安全

的双方约定的key,并同时加入其它参数进行混淆!

注意:这个key要在app里和我们的webapi里各保存相同的一份!

于是我们约定公式: 加密结果=md5(时间戳+随机数+key+post或者get的参数)

下面我们开始通过上述公式写代码:

于由我的环境是asp.net mvc 的,所以重写一个加密类apisecurityfilter

1、获取参数

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

   if (string.isnullorempty(timestamp) || string.isnullorempty(nonce) || string.isnullorempty(signature))
    throw new securityexception();

2、判断时间戳是否超过指定时间

 double ts = 0;
   bool timespanvalidate = double.tryparse(timestamp, out ts);

   bool falg = (datetime.utcnow - new datetime(1970, 1, 1, 0, 0, 0, 0)).totalmilliseconds - ts > 60 * 1000;

   if (falg || (!timespanvalidate))
    throw new securityexception();

3、post/delete/update 三种方式提取参数

 case "post":
    case "put":
    case "delete":

     stream stream = httpcontext.current.request.inputstream;
     streamreader streamreader = new streamreader(stream);
     sortedparams = new sorteddictionary<string, string>(new jsonserializer().deserialize<dictionary<string, string>>(new jsontextreader(streamreader)));

     break;

4、get 方式提取参数

case "get":

     idictionary<string, string> parameters = new dictionary<string, string>();

     foreach (string key in httpcontext.current.request.querystring)
     {
      if (!string.isnullorempty(key))
      {
       parameters.add(key, httpcontext.current.request.querystring[key]);
      }
     }

     sortedparams = new sorteddictionary<string, string>(parameters);

     break;

5、排序上述参数并拼接,形成我们要参与md5的约定公式中的第四个参数

   stringbuilder query = new stringbuilder();

   if (sortedparams != null)
   {
    foreach (var sort in sortedparams.orderby(k => k.key))
    {
     if (!string.isnullorempty(sort.key))
     {
      query.append(sort.key).append(sort.value);
     }
    }

    data = query.tostring().replace(" ", "");
   }

6、开始约定公式计算结果并对比传过的结果是否一致

 var md5staff = seedwork.utils.charhelper.md5(string.concat(timestamp + nonce + staffid + data), 32);

   if (!md5staff.equals(signature))
    throw new securityexception();

完整的代码如下:

public class apisecurityfilter : actionfilterattribute
 {
  public override void onactionexecuting(httpactioncontext actioncontext)
  {
   var request = actioncontext.request;

   var method = request.method.method;
   var staffid = "^***********************************$";

   string timestamp = string.empty, nonce = string.empty, signature = string.empty;

   if (request.headers.contains("timestamp"))
    timestamp = request.headers.getvalues("timestamp").firstordefault();

   if (request.headers.contains("nonce"))
    nonce = request.headers.getvalues("nonce").firstordefault();

   if (request.headers.contains("signature"))
    signature = request.headers.getvalues("signature").firstordefault();

   if (string.isnullorempty(timestamp) || string.isnullorempty(nonce) || string.isnullorempty(signature))
    throw new securityexception();

   double ts = 0;
   bool timespanvalidate = double.tryparse(timestamp, out ts);

   bool falg = (datetime.utcnow - new datetime(1970, 1, 1, 0, 0, 0, 0)).totalmilliseconds - ts > 60 * 1000;

   if (falg || (!timespanvalidate))
    throw new securityexception("timespanvalidate");

   var data = string.empty;
   idictionary<string, string> sortedparams = null;

   switch (method.toupper())
   {
    case "post":
    case "put":
    case "delete":

     stream stream = httpcontext.current.request.inputstream;
     streamreader streamreader = new streamreader(stream);
     sortedparams = new sorteddictionary<string, string>(new jsonserializer().deserialize<dictionary<string, string>>(new jsontextreader(streamreader)));

     break;
     
    case "get":

     idictionary<string, string> parameters = new dictionary<string, string>();

     foreach (string key in httpcontext.current.request.querystring)
     {
      if (!string.isnullorempty(key))
      {
       parameters.add(key, httpcontext.current.request.querystring[key]);
      }
     }

     sortedparams = new sorteddictionary<string, string>(parameters);

     break;

    default:
     throw new securityexception("defaultoptions");
   }

   stringbuilder query = new stringbuilder();

   if (sortedparams != null)
   {
    foreach (var sort in sortedparams.orderby(k => k.key))
    {
     if (!string.isnullorempty(sort.key))
     {
      query.append(sort.key).append(sort.value);
     }
    }

    data = query.tostring().replace(" ", "");
   }
  
   var md5staff = seedwork.utils.charhelper.md5(string.concat(timestamp + nonce + staffid + data), 32);

   if (!md5staff.equals(signature))
    throw new securityexception("md5staff");

   base.onactionexecuting(actioncontext);
  }

  public override void onactionexecuted(httpactionexecutedcontext actionexecutedcontext)
  {
   base.onactionexecuted(actionexecutedcontext);
  }
 }

7、最后在asp.net mvc 里加入配置上述类

 public static class webapiconfig
 {
  public static void register(httpconfiguration config)
  {
   // web api configuration and services
   config.filters.add(new apisecurityfilter());

   config.filters.add(new apihandleerrorattribute());

   // web api routes
   config.maphttpattributeroutes();

   config.routes.maphttproute(
    name: "defaultapi",
    routetemplate: "api/{controller}/{id}",
    defaults: new { id = routeparameter.optional }
   );
  }
 }

8、添加写入日志类

 public class apihandleerrorattribute: exceptionfilterattribute
 {
  /// <summary>
  /// add by laiyunba 
  /// </summary>
  /// <param name="filtercontext">context oop</param>
  public override void onexception(httpactionexecutedcontext filtercontext)
  {
   loggerfactory.createlog().logerror(messages.error_unmanagederror, filtercontext.exception);
  }
 }

9、利用微信小程序测试接口

 var data = {
  username: username,
  password: password,
  action: 'mobile',
  sms: ''
  };

  var timestamp = util.gettimestamp();
  var nonce = util.getnonce();

  if (username && password) {
  wx.request({
   url: rooturl + '/api/login',
   method: "post",
   data: data,
   header: {
   'content-type': 'application/json',
   'timestamp': timestamp,
   'nonce': nonce,
   'signature': util.getmd5staff(data, timestamp, nonce)
   },
   success: function (res) {
   if (res.data) {

1)其中getmd5staff函数:

function getmd5staff(querydata, timestamp, nonce) {

 var staffid = getstaffid();//保存的key与webapi同步
 var data = dictionaryorderwithdata(querydata);
 return md5.md5(timestamp + nonce + staffid + data);
}

2)dictionaryorderwithdata函数:

function dictionaryorderwithdata(dic) {
 //eg {x:2,y:3,z:1}
 var result = "";
 var sdic = object.keys(dic).sort(function (a, b) { return a.localecompare(b) });
 var value = "";

 for (var ki in sdic) {
 if (dic[sdic[ki]] == null) {
  value = ""
 }
 else {
  value = dic[sdic[ki]];
 }
 result += sdic[ki] + value;
 }

 return result.replace(/\s/g, "");
}

10、测试日志

laiyunbaapp error: 2 : 2017-10-18 09:15:25 unmanaged error in aplication, the exception information is exception:system.security.securityexception: 安全性错误。
 在 distributedservices.mainboundedcontext.filterattribute.apisecurityfilter.onactionexecuting(httpactioncontext actioncontext)
 在 system.web.http.filters.actionfilterattribute.onactionexecutingasync(httpactioncontext actioncontext, cancellationtoken cancellationtoken)
--- 引发异常的上一位置中堆栈跟踪的末尾 ---
 在 system.runtime.compilerservices.taskawaiter.throwfornonsuccess(task task)
 在 system.runtime.compilerservices.taskawaiter.handlenonsuccessanddebuggernotification(task task)
 在 system.web.http.filters.actionfilterattribute.<executeactionfilterasynccore>d__0.movenext()
--- 引发异常的上一位置中堆栈跟踪的末尾 ---
 在 system.runtime.compilerservices.taskawaiter.throwfornonsuccess(task task)
 在 system.runtime.compilerservices.taskawaiter.handlenonsuccessanddebuggernotification(task task)
 在 system.web.http.controllers.actionfilterresult.<executeasync>d__2.movenext()
--- 引发异常的上一位置中堆栈跟踪的末尾 ---
 在 system.runtime.compilerservices.taskawaiter.throwfornonsuccess(task task)
 在 system.runtime.compilerservices.taskawaiter.handlenonsuccessanddebuggernotification(task task)
 在 system.web.http.controllers.exceptionfilterresult.<executeasync>d__0.movenext()
失败的程序集的区域是:
mycomputer
logicaloperationstack=2017-10-18 09:15:25 
2017-10-18 09:15:25 datetime=2017-10-18t01:15:25.1000017z
2017-10-18 09:15:25 callstack= 在 system.environment.getstacktrace(exception e, boolean needfileinfo)
 在 system.environment.get_stacktrace()
 在 system.diagnostics.traceeventcache.get_callstack()
 在 system.diagnostics.tracelistener.writefooter(traceeventcache eventcache)
 在 system.diagnostics.tracesource.traceevent(traceeventtype eventtype, int32 id, string message)
 在 infrastructure.crosscutting.netframework.logging.tracesourcelog.traceinternal(traceeventtype eventtype, string message)
 在 infrastructure.crosscutting.netframework.logging.tracesourcelog.logerror(string message, exception exception, object[] args)
 在 system.web.http.filters.exceptionfilterattribute.onexceptionasync(httpactionexecutedcontext actionexecutedcontext, cancellationtoken cancellationtoken)
 在 system.web.http.filters.exceptionfilterattribute.<executeexceptionfilterasynccore>d__0.movenext()
 在 system.runtime.compilerservices.asynctaskmethodbuilder.start[tstatemachine](tstatemachine& statemachine)
 在 system.web.http.filters.exceptionfilterattribute.executeexceptionfilterasynccore(httpactionexecutedcontext actionexecutedcontext, cancellationtoken cancellationtoken)
 在 system.web.http.filters.exceptionfilterattribute.system.web.http.filters.iexceptionfilter.executeexceptionfilterasync(httpactionexecutedcontext actionexecutedcontext, cancellationtoken cancellationtoken)
 在 system.web.http.controllers.exceptionfilterresult.<executeasync>d__0.movenext()
 在 system.runtime.compilerservices.asynctaskmethodbuilder`1.start[tstatemachine](tstatemachine& statemachine)
 在 system.web.http.controllers.exceptionfilterresult.executeasync(cancellationtoken cancellationtoken)
 在 system.web.http.apicontroller.executeasync(httpcontrollercontext controllercontext, cancellationtoken cancellationtoken)
 在 system.web.http.dispatcher.httpcontrollerdispatcher.<sendasync>d__1.movenext()
 在 system.runtime.compilerservices.asynctaskmethodbuilder`1.start[tstatemachine](tstatemachine& statemachine)
 在 system.web.http.dispatcher.httpcontrollerdispatcher.sendasync(httprequestmessage request, cancellationtoken cancellationtoken)
 在 system.net.http.httpmessageinvoker.sendasync(httprequestmessage request, cancellationtoken cancellationtoken)
 在 system.web.http.dispatcher.httproutingdispatcher.sendasync(httprequestmessage request, cancellationtoken cancellationtoken)

至此,webapi加密工作已经全部完成,上述异常是直接访问url报的错误,必须在app环境下才可以正常访问。

 总结:webapi加密机密很多,像微信小程序,用户很难拿到客户端app的源码,想知道我们的key也是无从说起。当然,我们也得定期更新app版本。

像app for andriod or ios 可以使用双向证书,或者使用我们上述的方式,然后加固app,防止不怀好意的人破解得到key,当然不管如何,我们首先要走的都是https协议!

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。