天天看點

(NetFramework)WebApi Token+ 數字簽名認證

首先說一下具體思路和認證的過程:

1.用戶端登入成功後生成令牌token,并在伺服器儲存token,然後傳回給用戶端。

2.用戶端用時間戳+随機數+資料=數字簽名,通過加密算法生成數字簽名。

3.将token、使用者ID、時間戳、随機數、資料、數字簽名,儲存到http的header中,通過接口送出給伺服器。

4.由伺服器驗證通路的合法性。包括:token的有效性和是否過期、屏蔽爬蟲、數字簽名的真實性。也可加入系統級判斷:是否有權限通路。

一、用戶端登入

用戶端通過jquery對API接口送出請求:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title></title>
    <script src="Scripts/jquery-3.4.1.min.js"></script>
    <script type="text/javascript">

        //早期浏覽器版本跨域設定
        jQuery.support.cors = true;
        var token = "";
        function login() {
            $.ajax({
                type: "get",
                url: "http://localhost:44338/api/Login?uid=123&pwd=123&usertype=user",
                success: function (data, status) {
                    token = data.Data;
                    alert(data.Info);
                },
                error: function (e) {
                    console.log(e)
                }
            });
        }
    </script>
</head>
           

伺服器端API接口方法:

public class LoginController : AnonyController
    {
        [HttpGet]
        public IHttpActionResult Login(string uid,string pwd,string usertype)
        {
            ResultMsg result = null;
            if (uid=="123"&&pwd=="123")
            {
                string token = Guid.NewGuid().ToString();
                //token失效時間
                var timeout = DateTime.Now.AddDays(1);
                //添加token
                CacheManager.TokenInsert(token, uid, usertype, timeout);
                result = new ResultMsg { StatusCode = 1, Info = "登入成功",Data=token };
            }
            else
            {
                result = new ResultMsg { StatusCode = 0, Info = "登入失敗,賬号或密碼錯誤",Data=null };
            }
            return Json<ResultMsg>(result);
        }
}
           
/// <summary>
/// 添加令牌
/// </summary>
/// <param name="token">令牌</param>
/// <param name="uuid">使用者ID憑證</param>
/// <param name="userType">使用者類别</param>
/// <param name="timeout">過期時間</param>
public static void TokenInsert(string token, object uuid, string userType, DateTime timeout)
{
    CacheInit();
     // token不存在則添加
    if (!TokenIsExist(token))
    {
        DataTable dt = (DataTable)HttpRuntime.Cache["PASSPORT.TOKEN"];
        DataRow dr = dt.NewRow();
        dr["token"] = token;
        dr["uuid"] = uuid;
        dr["userType"] = userType;
        dr["timeout"] = timeout;
        dt.Rows.Add(dr);
        HttpRuntime.Cache["PASSPORT.TOKEN"] = dt;
        //儲存進資料庫
        //tempCacheService.Add_TempCaches(new List<TempCacheDTO_ADD>()
        //{
        //    new TempCacheDTO_ADD()
        //    {
        //        EndTime = timeout,
        //        UserAccountId = new Guid(uuid.ToString()),
        //        UserToken = new Guid(token),
        //        UserType = (UserType)Enum.Parse(typeof(UserType),userType)
        //    }
        //});
    }
    // token存在則更新過期時間
   else
    {
        TokenTimeUpdate(token, timeout);
        //更新資料庫
        //tempCacheService.Update_TempCaches(new Guid(token), timeout);
    }
}
           

完成了對登入的請求、伺服器token的儲存、傳回給用戶端token操作。

 二、用戶端生成數字簽名

生成數字簽名需要:時間戳、随機數、頁面送出的資料。時間戳:用于防止爬蟲重複送出。頁面送出的資料:用于防止爬蟲篡改資料,随機數算是一個變量因子。用戶端與伺服器端生成數字簽名的算法要一緻。用戶端數字簽名需要與伺服器的數字簽名對比來識别送出的資料是否被篡改。

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title></title>
    <script src="jquery-3.4.1.min.js"></script>
    <script src="jquery.md5.js"></script>
    <script type="text/javascript">

//早期浏覽器版本跨域設定
jQuery.support.cors = true;
        var token = "";
        var uid = "123";
        function login() {
            $.ajax({
                type: "get",
                url: "http://localhost:44338/api/Login?uid=" + uid + "&pwd=123&usertype=user",
                success: function (data, status) {
                    token = data.Data;
                    alert(data.Info);
                },
                error: function (e) {
                    console.log(e)
                }
            });
        }

        function Getdata() {

            //資料
            var jsonparam = { name1: "資料1", name2: "資料2", name3: "資料3" };

            var url = "http://localhost:44338/api/send/Getdata";

            GetAPI(jsonparam, url);
        }

        function GetAPI(jsonparam, url) {
            //時間戳
            var timestamp = new Date().getTime().toString();
            //随機數
            var nonce = Math.round(Math.random() * 100000000).toString();
            //拼接參數
            var builder = [];
            var query = [];
            for (var val in jsonparam) {
                builder.push(val);
                builder.push(jsonparam[val]);

                query.push(val + "=");
                query.push(encodeURIComponent(jsonparam[val]) + "&");
            }
            var data = builder.join('');
            var querystring = query.join('');
            querystring = querystring.substring(0, querystring.length - 1);

            //數字簽名
            var signatureStr = encodeURIComponent(timestamp + nonce + uid + token + data).toUpperCase();
            var signature = $.md5(signatureStr).toUpperCase();

            $.ajax({
                type: "get",
                url: url + "?" + querystring,
                beforeSend: function (request) {
                    request.setRequestHeader("timestamp", timestamp);
                    request.setRequestHeader("nonce", nonce);
                    request.setRequestHeader("signature", signature);
                    request.setRequestHeader("uid", uid);
                    request.setRequestHeader("token", token);
                },
                success: function (data, status) {
                    alert(data);
                },
                error: function (e) {
                    console.log(e)
                }
            });

        }
    </script>
</head>
<body>
    <input id="Button1" type="button" onclick="login();" value="登入" />
    <input id="Button3" type="button" onclick="Getdata();" value="GET請求資料" />
</body>

</html>
           

三、伺服器端驗證合法性

在調用請求的API接口之前,系統首先進入ActionFilterAttribute類進行攔截,是以我們定義一個類繼承此類。可以作為接口攔截器來判斷http請求的合法性。上伺服器代碼如下:

/// <summary>
    /// 調用接口過濾器
    /// </summary>
    public class ApiSecurityAttribute : ActionFilterAttribute
    {
        /// <summary>
        /// 判斷http請求的合法性
        /// </summary>
        /// <param name="actionContext"></param>
        public override void OnActionExecuting(System.Web.Http.Controllers.HttpActionContext actionContext)
        {
            ResultMsg resultMsg = null;
            var request = actionContext.Request;
            string method = request.Method.Method;
            string token = string.Empty,
                uid = String.Empty, 
                timestamp = string.Empty, 
                nonce = string.Empty, 
                signature = string.Empty;
            if (request.Headers.Contains("token"))
            {
                token = HttpUtility.UrlDecode(request.Headers.GetValues("token").FirstOrDefault());
            }
            if (request.Headers.Contains("uid"))
            {
                uid = HttpUtility.UrlDecode(request.Headers.GetValues("uid").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());
            }


            //判斷請求頭是否包含以下參數
            if (string.IsNullOrEmpty(token) || 
                string.IsNullOrEmpty(uid) || 
                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;
            }

            //判斷timespan是否有效
            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;//時間間隔2分鐘
            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;
            }


            //判斷token是否有效
            Token mytoken = (Token)HttpRuntime.Cache.Get(token);
            string signtoken = string.Empty;
            if (!CacheManager.TokenIsExist(token))
            {
                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;
            }

            bool result = false;
            switch (method)
            {
                case "POST":
                    Stream stream = HttpContext.Current.Request.InputStream;
                    string responseJson = string.Empty;
                    StreamReader streamReader = new StreamReader(stream);
                    result = SignExtension.ValidatePost(timestamp, nonce, uid, token, signature);
                    break;
                case "GET":
                    result = SignExtension.ValidateGet(timestamp,nonce,uid,token,signature);
                    break;
                default:
                    resultMsg = new ResultMsg();
                    resultMsg.StatusCode = 1;
                    resultMsg.Info ="";
                    resultMsg.Data =null;
                    actionContext.Response = HttpResponseExtension.ToJson(JsonConvert.SerializeObject(resultMsg));
                    base.OnActionExecuting(actionContext);
                    return;
            }

            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);
            }
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="actionExecutedContext"></param>
        public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
        {
            base.OnActionExecuted(actionExecutedContext);
        }
    }
           

文章相關源碼下載下傳位址:源碼下載下傳,希望對大家有幫助,謝謝。歡迎留言指正!!!