WEBAPI Security using token+ signature Verification

Source: Internet
Author: User
Tags http post md5 encryption tojson

First of all, ask you a question, how do you keep your data secure when you write an open API interface? Let's take a look at the security issues in the Open API interface, we are faced with many security issues when we request the server via HTTP POST or GET, for example:

    1. is the request source (identity) legal?
    2. The request parameter has been tampered with?
    3. The uniqueness of the request (not replicable) to prevent the request from being maliciously attacked

In order to ensure the security of the data in communication, we can use the token+ parameter signature method to carry on the correlation verification.

For example, our client needs to query the product information this operation for analysis, the client click the query button = = "Call server-side API query = =" Server side returned query results

I. Ways of not verifying

API Query Interface:

Client invocation: http://api.XXX.com/getproduct?id=value1

As above, this method is simple and rude, enter "Http://api" directly in the browser. Xxx.com/getproduct?id=value1 ", you can get the product list information, but there is a serious security problem in this way, there is no validation, we all get to the product list in this way, resulting in product information disclosure.
So how do you verify the identity of the caller? How do you prevent parameters from being tampered with? How to guarantee the uniqueness of the request? How to guarantee the uniqueness of the request and prevent the request from being attacked maliciously?

Ii. using token+ signature authentication to guarantee request security

The main principles of token+ signature authentication are: 1. Do a certification service, provide a certified WEBAPI, the user first access it to obtain the corresponding token

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

3. Each time the server receives the request from the corresponding user's token and request parameters, the server side to calculate the signature and client signature to do a comparison, if the authentication through the normal access to the appropriate API, validation failure to return the specific failure information

The specific code is as follows:

1. The user requests the authentication Service GetToken, saves the token in the server-side cache, and returns the corresponding token to the client (the request does not require signature authentication)

Public Httpresponsemessage GetToken (string staffid) {resultmsg resultmsg = null;            int id = 0; Determines if the argument is valid 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)); }//Insert cache 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);            }//Return token information resultmsg =new resultmsg ();            Resultmsg.statuscode = (int) statuscodeenum.success;            Resultmsg.info = "";            Resultmsg.data = token;        Return Httpresponseextension.tojson (Jsonconvert.serializeobject (resultmsg)); }

2. The client calls the server-side API, the request must be signed authentication, the signature method is as follows

(1) Get request: All request parameters are sorted alphabetically by request parameter name: Keyvaluekeyvalue...keyvalue string such as: Arong=1,mrong=2,crong=3 sorted as: arong=1, Crong =3,mrong=2 then concatenation the parameter name and the parameter value to get the argument string: Arong1crong3mrong2.

public static tuple<string,string> getquerystring (dictionary<string, string> parames) {// Step: Sort the dictionary alphabetically by key idictionary<string, string> sortedparams = new sorteddictionary<string, string> (p            Arames);            ienumerator<keyvaluepair<string, string>> dem = Sortedparams.getenumerator ();  Step Two: String all parameter names and values together StringBuilder query = new StringBuilder (""); Signature string StringBuilder querystr = new StringBuilder (""); URL parameter 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 request: Serializes the requested parameter object into a JSON-formatted string

Product Product = new Product () {Id = 1, Name = "Ann", Count = ten, Price = 58.8}; var data=jsonconvert.serializeobject (product);

(2) Add TimeSpan (timestamp) to the request header, Nonce (random number), StaffID (user ID), signature (signature parameter)

            Add header information            request. Headers.add ("StaffID", staffid.tostring ()); Currently requesting user StaffID request            . Headers.add ("timestamp", timestamp); Timestamp (in milliseconds) requested when the request was initiated            . Headers.add ("Nonce", nonce); Timestamp (in milliseconds) requested when the request was initiated            . Headers.add ("signature", Getsignature (Timestamp,nonce,staffid,data)); Digital signature of the current request content

(3) According to the request parameters to calculate the signature of this request, using Timespan+nonc+staffid+token+data (request parameter string) to get the SIGNSTR signature string, and then sorting and MD5 encryption to get the final signature signature string, Add to request header

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 is null, employee number is:" +staffid);            } var hash = System.Security.Cryptography.MD5.Create (); Splicing signature data var signstr = TimeStamp +nonce+ StaffID + token.            Signtoken.tostring () + data; The characters in the string are sorted in ascending order var sortstr = string.            Concat (Signstr.orderby (c = c));            var bytes = Encoding.UTF8.GetBytes (SORTSTR); Use MD5 to encrypt var md5val = Hash.computeHash (bytes);            Converts binary to uppercase hexadecimal StringBuilder result = new StringBuilder (); foreach (var c in md5val) {result.            Append (c.tostring ("X2")); } return result. ToString ().        ToUpper (); }

(4) Webapi receive the corresponding request, take out the timespan,nonc,staffid,signature data in the request header, according to the timespan to determine whether the request is invalid, according to StaffID take out the corresponding token to determine whether the token is invalid, The request parameter is taken out according to the request type, and then the server side calculates the request signature according to the same rules, and determines whether the signature data in the request header is the same, if the same is a legitimate request, the normal return data, if not the same, the request may be maliciously tampered with, prohibit access to the corresponding data , return the appropriate error message

Use global filters to intercept all API requests for uniform processing

 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 ());                The//gettoken method does not require signature verification 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 }}//Determine if the request header contains the following parameter 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            }//Determine if TimeSpan is valid 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 }//Determine if token is valid 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 ();            }//According to the request type splicing parameter NameValueCollection form = HttpContext.Current.Request.QueryString; String data = String.            Empty; Switch (method) {case "POST": Stream stream = HttpContext.Current.Request.In                    Putstream; String Responsejson = String.                    Empty;                    StreamReader StreamReader = new StreamReader (stream);                    data = Streamreader.readtoend ();                Break Case "GET"://First step: Remove all GET parameters idictionary<string, string> parameters = new Dicti                    Onary<string, string> (); for (int f = 0; F < form. Count; f++) {string key = form.                        KEYS[F]; Parameters.                    ADD (Key, Form[key]); }//Second step: Sort the dictionary alphabetically by key idictionary<string, string> sortedparams = new Sortedd                    Ictionary<string, string> (parameters);                    ienumerator<keyvaluepair<string, string>> dem = Sortedparams.getenumerator ();                    Step three: String all parameter names and values together 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); }    }

Then we test to verify the legality of the API request.

GET Request:

1. Get product data, pass parameter Id=1,name= "Wahaha", complete request is Http://localhost:14826/api/product/getproduct?id=1&name=wahaha

2. Request Header Add Timespan,staffid,nonce,signature field

3. When the value in data is Id1namewahaha, the value of the signature in the request header and the result computed by the server side is exactly the same, and when I modify data to ID1NAMEWAHAHA1, The server-side computed signature result and the signature that are submitted in the request header are not the same, and are represented as illegal requests.

4. Illegal requests will be identified as request parameters have been modified

Legitimate requests will return the corresponding product information.

POST request:

1.post object is serialized into a JSON string and submitted to the background, back to the corresponding product information

2. Background get parameter information of request

3. Determine if the signature is successful, the first request signature parameter signature and the server side of the calculation result is exactly the same, and then when the number of count in the request parameter from 10 to 100 after the server-side calculation of the result and request signature parameters signature different, So the request is illegal, the request is unlawful, similarly, if any other parameter is modified, the result of the calculation will be different from the signature parameter, the request is also recognized as an illegitimate request

Summarize:

Through the above case, we can see that the key to security is to participate in the signing token, the whole process of token is not involved in communication, so long as the token is not disclosed, the request will not be forged.

We then use the timestamp timestamp to verify that the request is out of date, which is not valid even if the full request link is taken.

Sign signature way to prevent information tampering and forgery to a certain extent, to ensure the security of communications

Source Address: Https://github.com/13138899620/TokenSign

WEBAPI Security using token+ signature Verification

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.