Sharing api interface verification module and api interface module

Source: Internet
Author: User
Tags key string

Sharing api interface verification module and api interface module

I. Preface

Permission verification is often encountered during development. It is also a encapsulated module. If we are a user, it usually requires a Tag feature or configuration, however, there are still many things worth exploring. Sometimes we will also use some open-source permission verification frameworks, but it is better to implement it by ourselves, and the sense of accomplishment (Force Grid. Go to the topic. This article mainly describes interface-side permission verification, which is used by each project. It is best to plug-in it and put it in Common, the new project can be used directly. This article has been written before web-based verification. If you are interested, take a look at ASP. net mvc Form verification.

II. Introduction

For our system, there are many ways to provide external access, such as webpage access and interface access. Different operations have different access permissions, such:

1. direct access.For data retrieval operations that do not affect the normal operation of the system and data, redundant verification is unnecessary and can be accessed directly at this time, such as obtaining the weather forecast information for the day, obtain website statistics.

2. form-based web verification.For websites, some web pages can be operated only after we log on. http requests are stateless, and it is impossible for users to log on every operation, in this case, you need to record the user's logon status somewhere. Form-based authentication usually records the login information in the Cookie, which is sent to the server with the request each time for verification. For example, in the blog Garden, the login information is recorded in a name named. in the Cookie of CNBlogsCookie (F12 can remove the cookie to observe the effect), this is an encrypted string, and the server decrypts it to obtain relevant information. Of course, although encrypted, requests are transmitted over the network, which may be stolen. To address this problem, https is usually used, which performs asymmetric encryption on requests, and cannot directly obtain our request information, greatly improving the security. We can see that the blog Park is also based on https.

  3. Signature-based api verification.For interfaces, there may be many access sources, and websites, mobile terminals, and desktop programs are all possible. At this time, Cookies cannot be used for this purpose. The theory of signature-based authentication is very simple. It has several important parameters:Appkey, random, timestamp, secretkey. The secretkey is not transmitted as the request. The server maintains a set of appkey-secretkey. For example, if you want to query the user balance, the request will be similar to:/api/user/querybalance? Userid = 1 & appkey = a86790776dbe45ca9032fc59bbc351cb & random = 191 & timestamp = 14826791236569260 & sign = 09d72f207ba8ca9c0fd0e5f8523340f5

Parameter Parsing:

1. appkey is used to find the corresponding secretkey for the server. Sometimes we allocate multiple pairs of appkey-secretkey, for example, one pair for Android and one pair for ios.

2. random and timestamp are designed to prevent Repaly Attacks. This is to prevent malicious requests from being cracked after the request is stolen. The timestamp parameter is required. The so-called timestamp refers to the total number of seconds from January 1, to the current. We specify a time, for example, 20 minutes. If the expiration time exceeds 20 minutes, the request is rejected if the interval between the current time and the timestamp exceeds 20 minutes. Random is not necessary, but it can also better prevent replay attacks. In theory, timestamp + random should be unique. In this case, we can cache it as a key in redis, if the requested timestamp + random can be obtained at the specified time, the request is rejected. There is another problem. What should I do if the time between the client and the server is not synchronized? This may require the client to correct the time, or increase the expiration time, for example, 30 minutes before expiration, or you can use the network time. It is also very common to prevent replay attacks. For example, you can adjust the mobile phone time to the previous time and then use the mobile phone bank. At this time, you will receive an error.

3. The sign is generated by certain rules. Here I useSign = md5 (httpmethod + url + timestamp + parameter string + secretkey)Generate. After the server receives the request, it first finds the secretkey through the appkey, Concatenates the key and then hash it, and compares it with the requested sign. Otherwise, it rejects the request. It should be noted that although we have done a lot of work, we still cannot prevent requests from being stolen. I have included timestamp in sign generation because timestamp is visible in the request, after a request is stolen, it can be completely modified and submitted again. If we participate in sign generation, the sign will be different once modified, improving security. The parameter string is a string generated by concatenating request parameters for a similar purpose to prevent parameter tampering. For example, if there are three parameters a = 1, B = 3, c = 2, the parameter string = a1b3c2 can also be sorted by value and then spliced to generate the parameter string.

For example, we recently used umeng's message PUSH Service. We can see that its signature generation rules are as follows, which is similar to our introduction.

Iii. coding implementation

Here we use Action Filter. For details, we can refer to the introduction to the execution process of several filters of ASP. net mvc through the source code. Through the introduction above, although there are many codes here, it is easy to understand. ApiAuthorizeAttribute is marked on Action or Controller and is defined as follows:

[AttributeUsage (AttributeTargets. class | AttributeTargets. method)] public class ApiAuthorizeAttribute: ApiBaseAuthorizeAttribute {private static string [] _ keys = new string [] {"appkey", "timestamp", "random", "sign "}; public override void OnAuthorization (AuthorizationContext context) {// whether anonymous access to if (context. actionDescriptor. isDefined (typeof (AllowAnonymousAttribute), false) {return;} HttpReq UestBase request = context. httpContext. request; string appkey = request [_ keys [0]; string timestamp = request [_ keys [1]; string random = request [_ keys [2]; string sign = request [_ keys [3]; apistandreconfig config = apistandreconfigprovider. config; if (string. isNullOrEmpty (appkey) {SetUnAuthorizedResult (context, ApiUnAuthorizeType. missAppKey); return;} if (string. isNullOrEmpty (timestamp) {SetUn AuthorizedResult (context, ApiUnAuthorizeType. missTimeStamp); return;} if (string. isNullOrEmpty (random) {SetUnAuthorizedResult (context, ApiUnAuthorizeType. missRamdon); return;} if (string. isNullOrEmpty (sign) {SetUnAuthorizedResult (context, ApiUnAuthorizeType. missSign); return;} // verify key string secretKey = string. empty; if (! SecretKeyContainer. container. tryGetValue (appkey, out secretKey) {SetUnAuthorizedResult (context, ApiUnAuthorizeType. keyNotFound); return;} // verification timestamp (timestamp refers to the total number of seconds since January 1,) long lt = 0; if (! Long. tryParse (timestamp, out lt) {SetUnAuthorizedResult (context, ApiUnAuthorizeType. timeStampTypeError); return;} long now = DateTime. now. subtract (new DateTime (1970, 1, 1 )). ticks; if (now-lt> new TimeSpan (0, config. minutes, 0 ). ticks) {SetUnAuthorizedResult (context, ApiUnAuthorizeType. pastRequet); return;} // verify the signature // httpmethod + url + parameter string + timestamp + secreptkey MD5Hasher md5 = new MD5Hash Er (); string parameterStr = GenerateParameterString (request); string url = request. Url. ToString (); url = url. Substring (0, url. IndexOf ('? '); String serverSign = md5.Hash (request. HttpMethod + url + parameterStr + timestamp + secretKey); if (sign! = ServerSign) {SetUnAuthorizedResult (context, ApiUnAuthorizeType. errorSign); return ;}} private string GenerateParameterString (HttpRequestBase request) {string parameterStr = string. empty; var collection = request. httpMethod = "GET "? Request. QueryString: request. Form; foreach (var key in collection. AllKeys. substring T (_ keys) {parameterStr + = key + collection [key]? String. Empty;} return parameterStr ;}}

The core code is parsed below. Apistandreconfig encapsulates some configuration information. For example, the expiration time we mentioned above is 20 minutes, but we want to customize it outside the module. Therefore, you can use an apistandreconfig package to register and obtain the package through apistandreconfigprovider. The definitions of apistandconfig and apistandconfigprovider are as follows:

    public class ApiStanderConfig    {        public int Minutes { get; set; }    }  
    public class ApiStanderConfigProvider    {        public static ApiStanderConfig Config { get; private set; }        static ApiStanderConfigProvider()        {            Config = new ApiStanderConfig()            {                Minutes = 20            };        }        public static void Register(ApiStanderConfig config)        {            Config = config;        }    }

As described above, the server maintains an appkey-secretkey set, which is implemented through a SecretKeyContainer. Its Container is a dictionary set, which is defined as follows:

    public class SecretKeyContainer    {        public static Dictionary<string, string> Container { get; private set; }        static SecretKeyContainer()        {            Container = new Dictionary<string, string>();        }        public static void Register(string appkey, string secretKey)        {            Container.Add(appkey, secretKey);        }        public static void Register(Dictionary<string, string> set)        {            foreach(var key in set)            {                Container.Add(key.Key, key.Value);            }        }    }

We can see that there are many condition judgments on them, and the errors are described differently. Therefore, I have defined an ApiUnAuthorizeType Error Type enumeration and DescriptionAttribute tag, as shown below:

Public enum ApiUnAuthorizeType {[Description ("time stamp type error")] TimeStampTypeError = 1000, [Description ("appkey missing")] MissAppKey = 1001, [Description ("missing timestamp")] MissTimeStamp = 1002, [Description ("missing random number")] MissRamdon = 1003, [Description ("missing signature")] MissSign = 1004, [Description ("appkey does not exist")] KeyNotFound = 1005, [Description ("Expiration request")] PastRequet = 1006, [Description ("Incorrect signature")] ErrorSign = 1007}
    public class DescriptionAttribute : Attribute    {        public string Description { get; set; }        public DescriptionAttribute(string description)        {            Description = description;        }    }

When the verification fails, SetUnAuthorizedResult is called and the request does not need to be processed. This method is implemented in the base class, as follows:

    public class ApiBaseAuthorizeAttribute : AuthorizeAttribute    {        protected virtual void SetUnAuthorizedResult(AuthorizationContext context, ApiUnAuthorizeType type)        {            UnAuthorizeHandlerProvider.ApiHandler(context, type);            HandleUnauthorizedRequest(context);        }        protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)        {            if (filterContext.Result != null)            {                return;            }            base.HandleUnauthorizedRequest(filterContext);        }    }

As you can see, it uses a delegate to handle the result based on the error type. The UnAuthorizeHandlerProvider is defined as follows:

    public class UnAuthorizeHandlerProvider    {        public static Action<AuthorizationContext, ApiUnAuthorizeType> ApiHandler { get; private set; }        static UnAuthorizeHandlerProvider()        {            ApiHandler = ApiUnAuthorizeHandler.Handler;        }        public static void Register(Action<AuthorizationContext, ApiUnAuthorizeType> action)        {            ApiHandler = action;        }    }    

By default, ApiUnAuthorizeHandler. Handler is used to process the result, but it can also be registered outside the module. The default processing is ApiUnAuthorizeHandler. Handler, as shown below:

    public class ApiUnAuthorizeHandler    {        public readonly static Action<AuthorizationContext, ApiUnAuthorizeType> Handler = (context, type) =>        {            context.Result = new StanderJsonResult()            {                Result = FastStatnderResult.Fail(type.GetDescription(), (int)type)            };        };    }

Its operation is to return a json result. Type. GetDescription is an extension method to obtain the description of DescriptionAttribute, as follows:

    public static class EnumExt    {        public static string GetDescription(this Enum e)        {            Type type = e.GetType();            var attributes = type.GetField(e.ToString()).GetCustomAttributes(typeof(DescriptionAttribute), false) as DescriptionAttribute[];            if(attributes.IsNullOrEmpty())            {                return null;            }            return attributes[0].Description;        }    }

Several json-related objects are involved here, but they should not affect reading. StanderResult, FastStanderResult, and StanderJsonResult. If you are interested, you can also take a look at them. They can be used in many places in the actual project, which can be used to standardize and simplify many operations. As follows:

    public class StanderResult    {        public bool IsSuccess { get; set; }        public object Data { get; set; }        public string Description { get; set; }        public int Code { get; set; }    }    public static class FastStatnderResult    {        private static StanderResult _success = new StanderResult() { IsSuccess = true };         public static StanderResult Success()        {            return _success;        }        public static StanderResult Success(object data, int code = 0)        {            return new StanderResult() { IsSuccess = true, Data = data, Code = code };        }        public static StanderResult Fail()        {            return new StanderResult() { IsSuccess = false };        }        public static StanderResult Fail(string description, int code = 0)        {            return new StanderResult() { IsSuccess = false, Description = description, Code = code };        }    }  
    public class StanderJsonResult : ActionResult    {        public StanderResult Result { get; set; }        public string ContentType { get; set; }        public Encoding Encoding { get; set; }        public override void ExecuteResult(ControllerContext context)        {            HttpResponseBase response = context.HttpContext.Response;            response.ContentType = string.IsNullOrEmpty(ContentType) ?                "application/json" : ContentType;            if (Encoding != null)            {                response.ContentEncoding = Encoding;            }            string json = JsonConvert.SerializeObject(Result);            response.Write(json);        }    }

4. Example

We register appkey-secretkey during program initialization, as shown in figure

// Register appkey-secretkey string [] appkey1 = ConfigurationReader. getStringValue ("appkey1 "). split (','); SecretKeyContainer. container. add (appkey1 [0], appkey1 [1]);

The following is simple to use. Mark the interface to be verified. For example

[ApiAuthorize] public ActionResult QueryBalance (int userId) {return Json ("query successful ");}

We enter a link on the webpage for testing: as shown in Figure

1. Input expiration time will prompt {"IsSuccess": false, "Data": null, "Description": "Expiration request", "Code": 1006}

2. If an incorrect signature is entered, the message {"IsSuccess": false, "Data": null, "Description": "Incorrect signature", "Code": 1007} is displayed}

Access is allowed only when all verifications are successful.

Of course, the actual project verification may be more complex and the conditions will be more, but all can be extended on this basis. As mentioned above, this algorithm can ensure that the request is legal and the parameters are not tampered with, but it still cannot ensure that the request is not stolen. To achieve higher security, you still need to Use https.

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.