Record the Webapi signature mechanism and webapi signature mechanism

Source: Internet
Author: User

Record the Webapi signature mechanism and webapi signature mechanism

First, the reason for writing this article is that the interface in a recent project has been manually called, resulting in database data being changed. Although it was inactive, it still caused my worries. I sorted out all the signature mechanisms related to Webapi.

1. When we develop interfaces, sometimes it is too troublesome to perform relevant verification or simply perform some simple verification, so that the client can directly call: for example:

Call Webapi interface: http://XXX.XXX.XX.XXX: 8123/Token/GetTest? ID = 123456

This method is simple and crude, directly entering "http://XXX.XXX.XX.XXX: 8123/Token/GetTest?" in the browser? ID = 123456 ", you can get the product list information, but this method will have serious security problems, without any verification, you can obtain the product list through this method, resulting in product information leakage. The following simple record uses TOKEN + signature authentication.

II,Use TOKEN + signature authentication to ensure request security

The main principles of token + signature authentication are as follows: 1. Implement an authentication service and provide an authenticated webapi. The user first accesses it to obtain the corresponding token.

2. the user takes the corresponding token and request parameters and the signature algorithm provided by the server to calculate the signature before accessing the specified api

3. each time the server receives a request, it obtains the token and request parameters of the corresponding user. The server calculates the signature again and compares it with the client signature. If the verification succeeds, the corresponding api is normally accessed, if verification fails, the specific failure information is returned.

The Code is as follows:

1. the user requests the authentication service GetToken, saves the token in the server cache, and returns the corresponding Token to the client (the request does not require signature authentication), using the GET call Method

[HttpGet] public IHttpActionResult GetToken (string signKey) {if (string. isNullOrEmpty (signKey) return Json <ResultMsg> (new ResultMsg (int) ExceptionStatus. parameterError, EnumExtension. getEnumText (ExceptionStatus. parameterError), null); // obtain the cached token string strKey = string Based on the signature ID. format ("{0} {1}", WebConfig. signKey, signKey); Token cacheData = HttpRuntime. cache. get (strKey) as Token; if (cacheData = null) {cacheData = new Token (); cacheData. signId = signKey; cacheData. timespan = DateTime. now. addDays (1); cacheData. signToken = Guid. newGuid (). toString ("N"); // insert cache. The cache time is one day. cache. insert (strKey, cacheData, null, cacheData. timespan, TimeSpan. zero);} // return token information return Json <ResultMsg> (new ResultMsg (int) ExceptionStatus. OK, EnumExtension. getEnumText (ExceptionStatus. OK), cacheData ));}

2. Client call method, GET or POST

(1) GET: must be added to the Request Header: timespan (timestamp), nonce (random number), signKey (key), signature (signature parameter)

Public static T Get <T> (string url, string paras, string signId, bool isSign = true) {HttpWebRequest webrequest = null; HttpWebResponse webresponse = null; string strResult = string. empty; try {webrequest = (HttpWebRequest) WebRequest. create (url + "? "+ Paras); webrequest. method = "GET"; webrequest. contentType = "application/json"; webrequest. timeout = 90000; // Add the header string timespan = GetTimespan (); string ran = GetRandom (10); webrequest. headers. add ("signKey", signId); DbLogger. logWriteMessage ("signKey:" + signId); webrequest. headers. add ("timespan", timespan); DbLogger. logWriteMessage ("timespan:" + timespan); webrequest. headers. add ("nonce", ra N); DbLogger. logWriteMessage ("nonce:" + ran); if (isSign) {string strSign = GetSignature (signId, timespan, ran, paras); webrequest. headers. add ("signature", strSign); DbLogger. logWriteMessage ("signature:" + strSign);} webresponse = (HttpWebResponse) webrequest. getResponse (); Stream stream = webresponse. getResponseStream (); StreamReader sr = new StreamReader (stream, Encoding. UTF8); strResult = sr. R EadToEnd ();} catch (Exception ex) {return JsonConvert. DeserializeObject <T> (ex. Message);} finally {if (webresponse! = Null) webresponse. Close (); if (webrequest! = Null) webrequest. Abort ();} return JsonConvert. DeserializeObject <T> (strResult );}

(2) POST is not written here. Similarly, you need to set header request parameters: timespan (timestamp), nonce (random number), signKey (key), and signature (signature parameter)

(3) Calculate the signature of this request based on the Request Parameters. Use timespan + nonc + signKey + token + data (request parameter string) to obtain the signStr signature string, then sort and encrypt the final signature string with MD5, and add it to the request header.

Public static string GetSignature (string signKey, string timespan, string nonce, string data) {string signToken = string. Empty; var result = GetToken <JObject> (); if (result! = Null) {if (result ["code"]. toString () = "200") {var tokena = JsonConvert. deserializeObject <JObject> (result ["result"]. toString (); if (tokena! = Null) signToken = tokena ["signToken"]. toString () ;}} var hash = MD5.Create (); string str = signKey + timespan + nonce + signToken + data; byte [] bytes = Encoding. UTF8.GetBytes (string. concat (str. orderBy (c => c); DbLogger. logWriteMessage ("str content:" + string. concat (str. orderBy (c => c); // use MD5 to encrypt var md5Val = hash. computeHash (bytes); // converts binary data to uppercase hexadecimal StringBuilder strSign = new StringBuilder (); foreach (var val in md5Val) {strSign. append (val. toString ("X2");} return strSign. toString ();}

(4) Webapi receives corresponding parameters, and obtains timespan (timestamp), nonce (random number), signKey (key), and signature (signature parameter) through the header ), determine whether the parameter is blank, whether the interface is within the validity period, whether the token is valid, and whether it is the same as the request's signature (signature). If yes, a normal result is returned. If the verification fails, an error message is returned.

Public override void OnActionExecuting (System. web. http. controllers. httpActionContext filterContext) {ResultMsg result = null; string signKey = string. empty, timespan = string. empty, nonce = string. empty, signature = string. empty; // determines whether the request message contains the var request = filterContext parameter. request; if (request. headers. contains ("signKey") signKey = request. headers. getValues ("signKey "). firstOrDefault (); if (requ Est. headers. contains ("timespan") timespan = request. headers. getValues ("timespan "). firstOrDefault (); if (request. headers. contains ("nonce") nonce = request. headers. getValues ("nonce "). firstOrDefault (); if (request. headers. contains ("signature") signature = request. headers. getValues ("signature "). firstOrDefault (); // if the method is GetToken, you do not need to verify if (filterContext. actionDescriptor. actionName. toLower () = "gett Oken ") {if (string. isNullOrEmpty (signKey) | string. isNullOrEmpty (timespan) | string. isNullOrEmpty (nonce) {result = new ResultMsg (int) ExceptionStatus. parameterError, EnumExtension. getEnumText (ExceptionStatus. parameterError), null); filterContext. response = HttpResponseExtension. toJson (result); base. onActionExecuting (filterContext); return;} else {base. onActionExecuting (filterContext); retur N ;}} DbLogger. logWriteMessage ("test parameter"); string signtoken = string. empty; // determines whether the parameter if (string. isNullOrEmpty (signKey) | string. isNullOrEmpty (timespan) | string. isNullOrEmpty (nonce) | string. isNullOrEmpty (signature) {result = new ResultMsg (int) ExceptionStatus. parameterError, EnumExtension. getEnumText (ExceptionStatus. parameterError), null); filterContext. response = HttpResponseExtension. toJ Son (result); base. onActionExecuting (filterContext); return;} DbLogger. logWriteMessage ("test whether it is within the validity period"); // judge whether it is within the validity period; double ts1 = 0; double ts2 = (DateTime. utcNow-new DateTime (1970, 1, 1, 0, 0, 0 )). totalMilliseconds; bool timespanValidate = double. tryParse (timespan, out ts1); double ts = ts2-ts1; bool falg = ts> int. parse (WebConfig. urlExpireTime) * 1000; if (! TimespanValidate | falg) {result = new ResultMsg (int) ExceptionStatus. URLExpireError, EnumExtension. getEnumText (ExceptionStatus. URLExpireError), null); filterContext. response = HttpResponseExtension. toJson (result); base. onActionExecuting (filterContext); return;} DbLogger. logWriteMessage ("test whether the token is valid"); // you can check whether the token is valid. cache. get (string. format ("{0} {1}", WebConfig. signKey, s IgnKey) as Token; if (token = null) {result = new ResultMsg (int) ExceptionStatus. tokenInvalid, EnumExtension. getEnumText (ExceptionStatus. tokenInvalid), null); filterContext. response = HttpResponseExtension. toJson (result); base. onActionExecuting (filterContext); return;} else signtoken = token. signToken; DbLogger. logWriteMessage ("determining http call method"); string data = string. empty; // judge the http call method string meth Od = request. method. method. toUpper (); 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": NameValueCollection form = HttpContext. current. request. queryString; // Step 1: obtain all the get parameter IDictionary <string, string> parameters = new Dic Tionary <string, string> (); for (int f = 0; f <form. count; f ++) {string key = form. keys [f]; parameters. add (key, form [key]);} // Step 2: sort the dictionary by Key in alphabetical order. IDictionary <string, string> sortedParams = new SortedDictionary <string, string> (parameters); IEnumerator <KeyValuePair <string, string> dem = sortedParams. getEnumerator (); // Step 3: string all parameter names and parameters together with StringBuilder query = new StringBuilder (); while (dem. move Next () {string key = dem. Current. Key; string value = dem. Current. Value; if (! String. isNullOrEmpty (key) {query. append (key ). append (value) ;}} data = query. toString (); break; default: result = new ResultMsg (int) ExceptionStatus. httpMehtodError, EnumExtension. getEnumText (ExceptionStatus. httpMehtodError), null); filterContext. response = HttpResponseExtension. toJson (result); base. onActionExecuting (filterContext); break;} DbLogger. logWriteMessage ("verify that the signature information conforms to"); // verify that the signature information conforms to the bo Ol valida = ValidateSign. Validate (signKey, timespan, nonce, signtoken, data, signature); if (! Valida) {result = new ResultMsg (int) ExceptionStatus. httpRequestError, EnumExtension. getEnumText (ExceptionStatus. httpRequestError), null); filterContext. response = HttpResponseExtension. toJson (result); base. onActionExecuting (filterContext); return;} else base. onActionExecuting (filterContext );}}

The following is a test:

GET request:

Returned results:

However, when we directly display the information in the browser or the information is modified by string, invalid requests will be identified as request parameters have been modified.

Determine whether the signature is successful. The signature parameter signature of the first request is exactly the same as the result calculated by the server. After the request parameter is modified, the result calculated by the server is different from the signature parameter, therefore, the request is invalid because the request is invalid. Similarly, if any other parameter is modified, the calculation result will be different from the signature parameter, and the request is also recognized as an invalid request.

Summary:

From the above case, we can see that the key to security lies in the token involved in the signature. during the entire process, the token is not involved in communication, so as long as the token is not disclosed, requests will not be forged.

Then, we use the timestamp to verify whether the request has expired. In this way, even if the complete request link is taken away, it is invalid.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.