Api接口安全验证规范文档

顾陌 发布时间:2017-11-28 分类:记事 阅读:10019次 添加评论


1. 前言

本规范旨在为公司各业务系统之间业务复用及整合的API提供接口调用与交互规范。同时,也作为未来公司业务系统内各应用模块之间以及各业务系统之间,基于面向服务的架构,以服务接口的方式,提供数据和各种功能的一种尝试。

1.1 术语和定义

缩写

全称

说明







1.2 适用对象

本规范仅适用于由服务器端发起调用请求、POST提交数据以及GET请求文本数据结果的API,统一采用UTF-8编码规则,采用JSON格式响应。

2. 数据包格式规范

2.1 URL定义

API 服务接口应提供REST风格的HTTP(HTTPS) 接口:

{protocol}://{domain}:{port}/{type}/{function}/{action}?{query}

变量

含义

示例

protocol

接口协议

HTTP、HTTPS

domain

网关ip地址或者网关域名

api.xxx.com

port

网关端口号

80、8080

Type

功能类别

SMSManage、UserManage等

function

功能名称

UserInfo、DepartmentInfo

action

操作类别(可空)

CREATE、SELECT、UPDATE、DELETE等

query由系统级参数部分和具体API调用参数部分组成,以key1=value1&key2=value2&…表示。

        对于采用POST请求的Open APIquery部分则是在POST请求体里。

2.2 请求头格式

名称

必填

描述

Content-Type

application/x-www-form-urlencoded;charset=utf-8

或application/json;charset=utf-8;

2.3 系统级请求参数

名称

必填

描述

appid

请求账号

timestamp

Unix时间戳,即从1970年1月1日0时0分0秒开始所经过的秒数(安全验证有效期60秒,防replay攻击)

sign_type

安全验证方式,默认为MD5。

sign

加密字符。

2.4 应用级请求参数

名称

类型

描述

pageindex

Int

分页索引,默认为1

pagesize

Int

分页量,表示每一页返回多少条数据,默认10,上限50

2.5 参数签名算法

系统提供MD5加密方式调用,默认为加密方式sign_type为MD5。MD5的加密步骤为:

步骤一:构造加密前的字符串

调用方将除“sign”以外的GET请求参数按照参数名称(Key),进行字典升序排序,并将Api接口地址(apiname)和排序后的参数用&拼接起来,参数的值需使用URL编码。

例如:/user/info/select?appid=123456&timestamp=1361461671。

步骤二:生成加密字符

使用MD5将步骤一生成的字符串+“&secret=秘钥”加密,并将加密后的字符串转换为小写。

执行完上述步骤,即可获取加密字符串,作为sign的值。

3. 接口请求示例

3.1 完整请求示例(C#)

    static void Main(string[] args)

    {

        string domain = "http://api.test.com";

        string appid = "123456";

        string secret = "XXXXXXXXXXXXX";

        int timestamp = GetNowTimeStamp();

 

        //准备参数

        SortedDictionary<string, string> paramlist = new SortedDictionary<string, string>();

        paramlist.Add("appid", appid);

        paramlist.Add("username", "测试字段");

        paramlist.Add("timestamp", timestamp.ToString());

 

        string URL = domain + GetSignURL(secret, "/user/info/select", paramlist);

 

        //string result = HttpPost(URL, "");

 

        System.Console.WriteLine(URL);

    }

 

    /// <summary>

    /// 获取加密后的请求地址

    /// </summary>

    /// <param name="secret"></param>

    /// <param name="apiname"></param>

    /// <param name="paramlist"></param>

    /// <returns></returns>

    public static string GetSignURL(string secret, string apiname, SortedDictionary<string, string> paramlist)

    {

        string queryString = "";

        //拼接字符串

        foreach (KeyValuePair<string, string> d in paramlist)

        {

            queryString += d.Key + "=" + HttpUtility.UrlEncode(d.Value) + "&";

        }

        string stringSignTemp = apiname + "?" + queryString.TrimEnd('&');

        string sign = GetMd5(stringSignTemp + "&secret=" + secret) .ToLower();

        return stringSignTemp + "&sign=" + sign;

    }

 

    /// <summary>

    /// 获取时间戳

    /// </summary>

    /// <param name="time"></param>

    /// <returns></returns>

    public static int GetTimeStamp(System.DateTime time)

    {

        System.DateTime startTime = TimeZone.CurrentTimeZone.ToLocalTime(new System.DateTime(1970, 1, 1));

        return (int)(time - startTime).TotalSeconds;

    }

    public static int GetNowTimeStamp()

    {

        return GetTimeStamp(DateTime.Now);

    }

 

    /// <summary>

    /// 获取字符串的MD5哈希值,默认编码为

    /// </summary>

    public static string GetMd5(string value, Encoding encoding = null)

    {

        if (encoding == null)

        {

            encoding = Encoding.UTF8;

        }

        byte[] bytes = encoding.GetBytes(value);

        return GetMd5(bytes);

    }

 

    /// <summary>

    /// 获取字节数组的MD5哈希值

    /// </summary>

    public static string GetMd5(byte[] bytes)

    {

        StringBuilder sb = new StringBuilder();

        System.Security.Cryptography.MD5 hash = new System.Security.Cryptography.MD5CryptoServiceProvider();

        bytes = hash.ComputeHash(bytes);

        foreach (byte b in bytes)

        {

            sb.AppendFormat("{0:x2}", b);

        }

        return sb.ToString();

    }

 

    /// <summary>

    /// HTTP请求

    /// </summary>

    /// <param name="url"></param>

    /// <param name="param"></param>

    /// <param name="ContentType"></param>

    /// <returns></returns>

    public static string HttpPost(string url, string param, string ContentType = "application/x-www-form-urlencoded")

    {

        var result = string.Empty;

        //注意提交的编码 这边是需要改变的 这边默认的是Default:系统当前编码

        byte[] postData = Encoding.UTF8.GetBytes(param);

 

        // 设置提交的相关参数

        System.Net.HttpWebRequest request = System.Net.WebRequest.Create(url) as System.Net.HttpWebRequest;

        request.Method = "POST";

        request.KeepAlive = false;

        request.AllowAutoRedirect = true;

        request.ContentType = ContentType;

        request.UserAgent = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727; .NET CLR  3.0.04506.648; .NET CLR 3.5.21022; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)";

        request.ContentLength = postData.Length;

 

        // 提交请求数据

        System.IO.Stream outputStream = request.GetRequestStream();

        outputStream.Write(postData, 0, postData.Length);

        outputStream.Close();

 

        System.Net.HttpWebResponse response;

        System.IO.Stream responseStream;

        System.IO.StreamReader reader;

        string srcString;

        response = request.GetResponse() as System.Net.HttpWebResponse;

        responseStream = response.GetResponseStream();

        reader = new System.IO.StreamReader(responseStream, Encoding.GetEncoding("UTF-8"));

        srcString = reader.ReadToEnd();

        result = srcString;   //返回值赋值

        reader.Close();

        return result;

    }

4. 服务端接口验证示例

4.1 安全验证步骤

通过请求的参数,多步验证。

  验证请求参数是否完整。

  验证时间戳是否有效(安全验证有效期60秒)。

  根据业务需求添加是否验证请求频次。

  根据业务需求验证时间戳是否已经请求过。

  根据业务需求验证IP限制、时间限制等。

  验证加密字符串。

4.2 安全验证示例(C#)

建议将安全验证应用在MVC的过滤器里。

/// <summary>

/// API请求 安全验证过滤器

/// </summary>

[AttributeUsage(AttributeTargets.All)]

public class SafeTokenAuthAttribute : System.Web.Mvc.ActionFilterAttribute

{

    public override void OnActionExecuting(ActionExecutingContext filterContext)

    {

        var request = filterContext.HttpContext.Request;

 

        string appid = request.QueryString["appid"];

        if (string.IsNullOrWhiteSpace(appid))

        {

            filterContext.Result = new JsonResult() { Data = new ApiResult() { code = 401, message = "请求账号为空" }, JsonRequestBehavior = JsonRequestBehavior.AllowGet };

            base.OnActionExecuting(filterContext);

            return;

        }

 

        string sign = request.QueryString["sign"];

        if (string.IsNullOrWhiteSpace(sign))

        {

            filterContext.Result = new JsonResult() { Data = new ApiResult() { code = 402, message = "加密字符不能为空" }, JsonRequestBehavior = JsonRequestBehavior.AllowGet };

            base.OnActionExecuting(filterContext);

            return;

        }

 

        string timestampStr = request.QueryString["timestamp"] ?? "0";

        int timestamp = 0;

        int.TryParse(timestampStr, out timestamp);

 

        //当前时间戳

        int nowtimestamp = CommonHelper.GetNowTimeStamp();

        //调试开发期间暂时禁用

        if ((nowtimestamp - timestamp) > 120 || (nowtimestamp - timestamp) < -60)//前后1分钟的有效期

        {

            filterContext.Result = new JsonResult() { Data = new ApiResult() { code = 403, message = "请求已过期" }, JsonRequestBehavior = JsonRequestBehavior.AllowGet };

            base.OnActionExecuting(filterContext);

            return;

        }

 

        var filterResult = new JsonResult() { Data = new ApiResult() { code = 400, message = "验证未通过" }, JsonRequestBehavior = JsonRequestBehavior.AllowGet };

 

 

        var apiname = request.Url.AbsolutePath;

        string queryString = "";

        System.Collections.Specialized.NameValueCollection coll = request.QueryString;

        String[] requestItem = coll.AllKeys;

        for (var i = 0; i < requestItem.Length; i++)

        {

            if (requestItem[i] != "sign")

            {

                queryString += requestItem[i] + "=" + HttpUtility.UrlEncode(coll[requestItem[i]]) + "&";

            }

        }

 

        string sign_type = request.QueryString["sign_type"] ?? "MD5";

 

        //通过appid从数据库或缓存获取对应的secret   todo

        string secret = "XXXXXXXXXXXXX";

 

        bool isValidate = false;

        if (sign_type == "MD5")

        {

            string stringSignTemp = apiname + "?" + queryString.TrimEnd('&');

            string encrypt =GetMd5(stringSignTemp + "&secret=" + secret).ToLower();

 

            if (encrypt == sign)

            {

                //MD5验证通过

                isValidate = true;

            }

            else

            {

                filterContext.Result = filterResult;

                base.OnActionExecuting(filterContext);

                return;

            }

        }

 

        //未通过验证

        if (!isValidate)

        {

            filterContext.Result = filterResult;

            base.OnActionExecuting(filterContext);

            return;

        }

    }

}

 

MVC的过滤器的使用方法:

[SafeTokenAuth]

public ActionResult Index()

{

    return Content("验证通过才能访问");

}

 

5. 服务端响应格式

5.1 响应输出格式

服务端正常输入应该符合如下的JSON格式:

  http响应头中的Content-Type指定为application/json, charset=utf-8

  字符串编码格式是UTF-8

  输出格式为:

{

      "code": "响应码(数值型)":,

      "message": "响应描述(字符串)",

      "data":"[response body]JSON字符串)"

}

 

[response body]可为空。

5.2 响应输出示例

API接口请求成功时候响应示例:

{

      "code": 200,

      "message": "请求成功",

      "data": null

}

API接口请求失败时候响应示例:

{

      "code": 402,

      "message": "请求已过期",

      "data": null

}

5.3 响应状态码类别

名称

类型

描述

200

Int

请求成功

100

Int

1** 表示请求的参数错误等问题

300

Int

3** 表示业务逻辑等问题

400

Int

4** 表示安全验证等问题

500

Int

5** 表示内部错误等问题

5.4 响应状态码字典

名称

描述

200

请求成功



400

安全验证未通过

401

请求账号为空

402

加密字符为空

403

请求已过期



 


暂无留言

发表评论:

◎欢迎您的参与讨论。