asp.net An example of MVC webapi interface encryption method

Time:2020-11-30

In many projects, because webapi is open to the outside world, at this time, we have to consider the security of interface exchange data.

There are also many security mechanisms. For example, when Andriod and webapi exchange data, the two-way certificate method can be used, but the development cost is relatively high,

Today we are not going to introduce this aspect of knowledge, let’s talk about a simpler and more common security exchange mechanism

Here’s a reminder to readers,At present, all encryption mechanisms are not absolutely secure

Our goal is that any user or software that obtains our webapi interface URL and uses it to access the address again is invalid!

To achieve this goal, we have to add a timestamp to the URL, but that’s not enough. Users can modify our timestamp!

Therefore, we can encrypt the timestamp with MD5, but this is still not enough. Users can directly encrypt our timestamp MD5. Therefore, we need to introduce an absolute security

The key agreed by both parties, and add other parameters to confuse!

Note: this key should be saved in the app and our webapi!

So we agree the formula: encryption result = MD5 (timestamp + random number + key + post or get parameter)

Let’s start to write the code according to the above formula:

My environment is asp.net MVC, so rewrite an encryption class apisecurityfilter

1. Get parameters


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. Determine whether the timestamp exceeds the specified time


 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. Three ways to extract parameters: 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 method to extract parameters


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. The above parameters are sorted and spliced to form the fourth parameter in the Convention formula that we want to participate in 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. Start to agree formula calculation results and compare whether the results are consistent


 var md5Staff = Seedwork.Utils.CharHelper.MD5(string.Concat(timestamp + nonce + staffId + data), 32);

   if (!md5Staff.Equals(signature))
    throw new SecurityException();

The complete code is as follows:


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. At the end of the day asp.net MVC add configuration of the above class


 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. Add write log class


 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. Using wechat applet to test the interface


 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) Where getmd5staff function:

function getMD5Staff(queryData, timestamp, nonce) {

 Var staffid = getstaffid(); // the saved key is synchronized with webapi
 var data = dictionaryOrderWithData(queryData);
 return md5.md5(timestamp + nonce + staffId + data);
}

2) Dictionaryorderwithdata function:


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. Test log

LaiyunbaApp Error: 2 : 2017-10-18 09:15:25 Unmanaged error in aplication, the exception information is  Exception:System.Security.SecurityException : Security error.
 In DistributedServices.MainBoundedContext.FilterAttribute . ApiSecurityFilter.OnActionExecuting (HttpActionContext actionContext)
 In System.Web.Http . Filters.ActionFilterAttribute .OnActionExecutingAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
---The end of the stack trace in the last position where the exception was thrown---
 In System.Runtime.CompilerServices . TaskAwaiter.ThrowForNonSuccess (Task task)
 In System.Runtime.CompilerServices . TaskAwaiter.HandleNonSuccessAndDebuggerNotification (Task task)
 In System.Web.Http . Filters.ActionFilterAttribute .<ExecuteActionFilterAsyncCore>d__ 0.MoveNext()
---The end of the stack trace in the last position where the exception was thrown---
 In System.Runtime.CompilerServices . TaskAwaiter.ThrowForNonSuccess (Task task)
 In System.Runtime.CompilerServices . TaskAwaiter.HandleNonSuccessAndDebuggerNotification (Task task)
 In System.Web.Http . Controllers.ActionFilterResult .<ExecuteAsync>d__ 2.MoveNext()
---The end of the stack trace in the last position where the exception was thrown---
 In System.Runtime.CompilerServices . TaskAwaiter.ThrowForNonSuccess (Task task)
 In System.Runtime.CompilerServices . TaskAwaiter.HandleNonSuccessAndDebuggerNotification (Task task)
 In System.Web.Http . Controllers.ExceptionFilterResult .<ExecuteAsync>d__ 0.MoveNext()
The region of the failed assembly is:
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 = on System.Environment.GetStackTrace (Exception e, Boolean needFileInfo)
 In System.Environment.get_ StackTrace()
 In System.Diagnostics.TraceEventCache .get_ Callstack()
 In System.Diagnostics.TraceListener .WriteFooter(TraceEventCache eventCache)
 In System.Diagnostics.TraceSource .TraceEvent(TraceEventType eventType, Int32 id, String message)
 In Infrastructure.Crosscutting.NetFramework . Logging.TraceSourceLog.TraceInternal (TraceEventType eventType, String message)
 In Infrastructure.Crosscutting.NetFramework . Logging.TraceSourceLog.LogError (String message, Exception exception, Object[] args)
 In System.Web.Http . Filters.ExceptionFilterAttribute .OnExceptionAsync(HttpActionExecutedContext actionExecutedContext, CancellationToken cancellationToken)
 In System.Web.Http . Filters.ExceptionFilterAttribute .<ExecuteExceptionFilterAsyncCore>d__ 0.MoveNext()
 In System.Runtime.CompilerServices .As yncTaskMethodBuilder.Start [TStateMachine](TStateMachine& stateMachine)
 In System.Web.Http . Filters.ExceptionFilterAttribute .ExecuteExceptionFilterAsyncCore(HttpActionExecutedContext actionExecutedContext, CancellationToken cancellationToken)
 In System.Web.Http . Filters.ExceptionFilterAttribute . System.Web.Http . Filters.IExceptionFilter.ExecuteExceptionFilterAsync (HttpActionExecutedContext actionExecutedContext, CancellationToken cancellationToken)
 In System.Web.Http . Controllers.ExceptionFilterResult .<ExecuteAsync>d__ 0.MoveNext()
 In System.Runtime.CompilerServices .AsyncTaskMethodBuilder`1.Start[TStateMachine](TStateMachine& stateMachine)
 In System.Web.Http . Controllers.ExceptionFilterResult .ExecuteAsync(CancellationToken cancellationToken)
 In System.Web.Http . ApiController.ExecuteAsync (HttpControllerContext controllerContext, CancellationToken cancellationToken)
 In System.Web.Http . Dispatcher.HttpControllerDispatcher .<SendAsync>d__ 1.MoveNext()
 In System.Runtime.CompilerServices .AsyncTaskMethodBuilder`1.Start[TStateMachine](TStateMachine& stateMachine)
 In System.Web.Http . Dispatcher.HttpControllerDispatcher .SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
 In System.Net.Http . HttpMessageInvoker.SendAsync (HttpRequestMessage request, CancellationToken cancellationToken)
 In System.Web.Http . Dispatcher.HttpRoutingDispatcher .SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)

So far, the webapi encryption work has been completed, the above exception is a direct access to the URL error, must be in the app environment can be normal access.

Conclusion: there are many secrets in webapi encryption, such as wechat applet, it is difficult for users to get the source code of client app, and it is impossible to know our key. Of course, we also need to update the app version regularly.

For example, app for Android or IOS can use the two-way certificate, or use the method mentioned above, and then consolidate the app to prevent malicious people from cracking the key. Of course, no matter what, the first thing we have to go is the HTTPS protocol!

The above is the whole content of this article, I hope to help you in your study, and I hope you can support developeppaer more.

Recommended Today

Review of SQL Sever basic command

catalogue preface Installation of virtual machine Commands and operations Basic command syntax Case sensitive SQL keyword and function name Column and Index Names alias Too long to see? Space Database connection Connection of SSMS Connection of command line Database operation establish delete constraint integrity constraint Common constraints NOT NULL UNIQUE PRIMARY KEY FOREIGN KEY DEFAULT […]