Asp.Net WebApi一個簡單的Token驗證

一、前言:

WebAPI主要開放數據給手機APP,Pad,其餘須要得知數據的系統,或者軟件應用。Web 用戶的身份驗證,及頁面操做權限驗證是B/S系統的基礎功能。我上次寫的《Asp.Net MVC WebAPI的建立與前臺Jquery ajax後臺HttpClient調用詳解》這種跟明顯安全性不是那麼好,因而乎這個就來了 ,用戶須要訪問的API都必須帶有票據過來,說白了就是登錄以後含有用戶信息的Token。開始擼...javascript

二、新建一個WebApi項目,在App_Start文件夾下面新建一個BaseApiController控制器,這是基礎的Api控制器,後面有要驗證的接口都繼承這個控制器:

using LoginReqToken.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http;
using System.Web.Mvc;

namespace LoginReqToken.App_Start
{
    /// <summary>
    /// 基礎Api控制器  全部的都繼承他
    /// </summary>
    public class BaseApiController : ApiController
    {
       
       /// <summary>
       /// 構造函數賦值
       /// </summary>
        public BaseApiController()
        {
            TokenValue = HttpContext.Current.Session[LoginID] ?? "";
            HttpContext.Current.Request.Headers.Add("TokenValue", TokenValue.ToString());
        }
        /// <summary>
        /// 數據庫上下文
        /// </summary>
        public WYDBContext db = WYDBContextFactory.GetDbContext();
        /// <summary>
        /// token值 登陸後賦值請求api的時候添加到header中
        /// </summary>
        public static object TokenValue { get; set; } = "";
        /// <summary>
        /// 登陸者帳號
        /// </summary>
        public static string LoginID { get; set; } = "";
    }
}

這個構造函數裏主動加一個header頭信息 ,由於每次訪問的時候都要執行構造函數,在那邊驗證的時候都要從Header中取出來,計算出用戶名 是否跟Session緩存的一致這樣判斷的html

三、在建一個TokenCheckFilter.cs繼承AuthorizeAttribute重寫基類的驗證方式,重寫HandleUnauthorizedRequest

using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Web;
using System.Web.Helpers;
using System.Web.Http;
using System.Web.Http.Controllers;
using System.Web.Security;
namespace LoginReqToken.App_Start
{
    /// <summary>
    /// token驗證
    /// </summary>
    public class TokenCheckFilter: AuthorizeAttribute
    {

        /// <summary>
        /// 重寫基類的驗證方式,加入自定義的Ticket驗證
        /// </summary>
        /// <param name="actionContext"></param>
        public override void OnAuthorization(HttpActionContext actionContext)
        {
            var content = actionContext.Request.Properties["MS_HttpContext"] as HttpContextBase;
            //獲取token(請求頭裏面的值)
            var token = HttpContext.Current.Request.Headers["TokenValue"] ?? "";
            //是否爲空
            if (!string.IsNullOrEmpty(token.ToString()))
            {
                //解密用戶ticket,並校驗用戶名密碼是否匹配
                if (ValidateTicket(token.ToString()))
                    base.IsAuthorized(actionContext);
                else
                    HandleUnauthorizedRequest(actionContext);
            }
            //若是取不到身份驗證信息,而且不容許匿名訪問,則返回未驗證403
            else
            {
                var attributes = actionContext.ActionDescriptor.GetCustomAttributes<AllowAnonymousAttribute>().OfType<AllowAnonymousAttribute>();
                bool isAnonymous = attributes.Any(a => a is AllowAnonymousAttribute);
                if (isAnonymous) base.OnAuthorization(actionContext);
                else HandleUnauthorizedRequest(actionContext);
            }
        }

        //校驗用戶名密碼(對Session匹配,或數據庫數據匹配)
        private bool ValidateTicket(string encryptToken)
        {
            //解密Ticket
            var strTicket = FormsAuthentication.Decrypt(encryptToken).UserData;
            //從Ticket裏面獲取用戶名和密碼
            var index = strTicket.IndexOf("&");
            string userName = strTicket.Substring(0, index);
            string password = strTicket.Substring(index + 1);
            //取得session,不經過說明用戶退出,或者session已通過期
            var token = HttpContext.Current.Session[userName];
            if (token == null)
                return false;
            //對比session中的令牌
            if (token.ToString() == encryptToken)
                return true;
            return false;
        }
        /// <summary>
        /// 重寫HandleUnauthorizedRequest
        /// </summary>
        /// <param name="filterContext"></param>
        protected override void HandleUnauthorizedRequest(HttpActionContext filterContext)
        {
            base.HandleUnauthorizedRequest(filterContext);

            var response = filterContext.Response = filterContext.Response ?? new HttpResponseMessage();
            //狀態碼401改成其餘狀態碼來避免被重定向。最合理的是改成403,表示服務器拒絕。
            response.StatusCode = HttpStatusCode.Forbidden;
            var content = new 
            {
                success = false,
                errs = new[] { "服務端拒絕訪問:你沒有權限?,或者掉線了?" }
            };
            response.Content = new StringContent(Json.Encode(content), Encoding.UTF8, "application/json");
        }

    }
}

四、在WebApiConfig.cs配置文件裏面修改一下路由加上/{action},這樣就能調用到具體的哪個了

 

 

 

Webapi默認是不支持Session的,因此咱們須要在Global加載時候添加對Session的支持,在Global.asax裏面重寫Application_PostAuthorizeRequest,否則運行調用會直接異常前端

    public class WebApiApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();
            GlobalConfiguration.Configure(WebApiConfig.Register);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);
        }
        /// <summary>
        /// 重寫Application_PostAuthorizeRequest
        /// </summary>
        protected void Application_PostAuthorizeRequest()
        {
            //對Session的支持,否則運行調用會直接異常
            HttpContext.Current.SetSessionStateBehavior(System.Web.SessionState.SessionStateBehavior.Required);
        }
    }

 

 

五、如今來寫一個登錄,新建一個控制器LoginController繼承BaseApiController 裏面寫一個登錄的方法Login 登錄頁面就直接在Home的index裏面寫一個簡單的就好了這個控制器訪問就不受限制了加上註解AllowAnonymous

[AllowAnonymous]
    public class LoginController : BaseApiController
    {
        [HttpGet]
        public object Login(string uName, string uPassword)
        {
            var user = db.Users.Where(x => x.LoginID == uName && x.Password == uPassword).FirstOrDefault();
            if (user==null)
            {
                return Json(new { ret = 0, data = "", msg = "用戶名密碼錯誤" });
            }
            FormsAuthenticationTicket token = new FormsAuthenticationTicket(0, uName, DateTime.Now, DateTime.Now.AddHours(12), true, $"{uName}&{uPassword}", FormsAuthentication.FormsCookiePath);
            //返回登陸結果、用戶信息、用戶驗證票據信息
            var _token = FormsAuthentication.Encrypt(token);
            //將身份信息保存在session中,驗證當前請求是不是有效請求
            LoginID = uName;
            TokenValue = _token;
            HttpContext.Current.Session[LoginID] = _token;
            return Json(new { ret = 1, data = _token, msg = "登陸成功!" });
        }
    }

 

登錄頁面 簡單而粗暴java

 

<br /><br />
<input type="text" name="txtLoginID" id="txtLoginID"  />
<br /><br />
<input type="password" name="txtPassword" id="txtPassword"  />
<br /><br />
<input type="button" id="btnSave" value="登陸驗證" />
<script type="text/javascript" src="~/Scripts/jquery-3.3.1.js"></script>
<script type="text/javascript">
    $(document).ready(function () {
        $("#btnSave").click(function () {
            $.ajax({
                type: "GET",
                url: "/Api/Login/Login",
                dataType: "json",
                data: { "uName": $("#txtLoginID").val(), "uPassword": $("#txtPassword").val()},
                success: function (data) {
                    if (data.ret > 0) {
                        alert(data.msg+"Token:  "+data.data);
                    }
                    else {
                        alert(data.msg);
                    }
                },
                error: function (ret) {
                    console.log(ret);
                }
            });
        });
    });
</script>

 

 

 

 登錄這個我是寫了連接數據庫的本身練習能夠最易更改一個固定的值 如今應該能夠看到返回的Token數據了jquery

 

 

 

 六、如今就能夠寫Api了 都繼承BaseApiController這個控制器的方法上面須要驗證的都要加上驗證的註解,我是整個控制都要就直接寫在類上面了,隨便寫一個舉舉例子,就好比全國省市縣的查詢

using LoginReqToken.App_Start;
using LoginReqToken.Models;
using LoginReqToken.Models.DTO;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;

namespace LoginReqToken.Controllers
{

    /// <summary>
    /// 區域查詢
    /// </summary>
    [TokenCheckFilter]
    public class AreaOpController : BaseApiController
    {
        /// <summary>
        /// 獲取所有區域
        /// </summary>
        /// <returns></returns>
        public Result GetAllAreas()
        {
            var data = db.AddressAll.OrderBy(x => x.ID);
            if(data.Count()>0)
            {
                var ret = new Result()
                {
                    Ret = 1,
                    Code = "200",
                    Msg = "獲取數據成功",
                    Data = JsonConvert.SerializeObject(data)

                };
                return ret;
            }
            else
            {
                var ret = new Result()
                {
                    Ret = 0,
                    Code = "400",
                    Msg = "接口失敗異常",
                    Data = ""

                };
                return ret;
            }
        }
        /// <summary>
        /// 查詢某個省市直轄市自治區下全部的信息
        /// </summary>
        /// <param name="name">省名稱(全名)</param>
        /// <returns></returns>
        public Result GetProvinceByName(string name)
        {
            var codeID = db.AddressAll.FirstOrDefault(x => x.Name == name)?.ID;
            if(codeID<=0)
            {
                var ret = new Result()
                {
                    Ret = 1,
                    Code = "F",
                    Msg = "沒有查到相關數據",
                    Data = ""

                };
                return ret;
            }
            var bb = db.AddressAll.Where(x=>x.ID>0).AsEnumerable();
            var data = GetProvinceCity(bb,codeID).ToList();
            if (data.Count() > 0)
            {
                var ret = new Result()
                {
                    Ret = 1,
                    Code = "200",
                    Msg = "獲取數據成功",
                    Data = JsonConvert.SerializeObject(data)

                };
                return ret;
            }
            else
            {
                var ret = new Result()
                {
                    Ret = 0,
                    Code = "500",
                    Msg = "查詢不到數據或者接口調用出錯",
                    Data = ""

                };
                return ret;
            }

        }
        /// <summary>
        /// 遞歸獲取樹形數據
        /// </summary>
        /// <param name="areasDTOs"></param>
        /// <param name="parentID"></param>
        /// <returns></returns>
        public IEnumerable<object> GetProvinceCity(IEnumerable<AddressAll> areasDTOs,int? parentID)
        {
            var data = areasDTOs as AddressAll[] ?? areasDTOs.ToArray();
            var ret = data.Where(n => n.ParentID == parentID).Select(n => new
            {
                n.ID,
                n.Code,
                n.ParentID,
                n.Name,
                n.LevelNum,
                n.OrderNum,
                children = GetProvinceCity(data, n.ID)
            });
            return ret;
        }
    }
}

 

記錄一個EF隨意取數據庫條數信息是這麼寫的 var data = db.CnblogsList.OrderBy(p => Guid.NewGuid()).Take(100);ajax

如今看效果圖數據庫

 

 

 沒有登錄的時候是進不去的 postman上面的效果也看一下json

 

 

效果都是同樣的,若是登陸了就能夠直接訪問 了 不用加參數 ,只有方法須要參數的就能夠加 api

 

 

 

 

 

 這裏貼一個調用的代碼:瀏覽器

 HttpClient bb = new HttpClient();
            //獲取端口
            HttpContent httpContent = new StringContent("");
            httpContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
            
             var dl = bb.GetAsync("http://localhost:63828/api/Login/login?uName=admin&uPassword=admin888").Result.Content.ReadAsStringAsync().Result;
            var token = JsonConvert.DeserializeObject<Result>(dl);
           
            for (var i=0;i<100;i++)
            {
                
                var ret = bb.GetAsync("http://localhost:63828/api/Cnblog/GetAllArtic").Result.Content.ReadAsStringAsync().Result;
            }

 

七、總結:

1)、整體思路,若是是合法的Http請求,在Http請求頭中會有用戶身份的票據信息,服務端會讀取票據信息,並校驗票據信息是否完整有效,若是知足校驗要求,則進行業務數據的處理,並返回給請求發起方;

2) 若是沒有票據信息,或者票據信息不是合法的,則返回「未受權的訪問」異常消息給前端,由前端處理此異常。

3)、登陸的時候判斷用戶名跟密碼對不對,對了就生成用戶信息的Token,Session保存一個Token,BaseApiController裏面的登陸名跟Token也賦值了。保存這些票據信息。

4)、當用戶有權限操做頁面或頁面元素時,跳轉到頁面,並由頁面Controller提交業務數據處理請求到api服務器; 若是用戶沒有權限訪問該頁面或頁面元素時,則顯示「未受權的訪問操做」,跳轉到系統異常處理頁面。

5)、 api業務服務處理業務邏輯,並將結果以Json 數據返回,返回渲染後的頁面給瀏覽器前端,並呈現業務數據到頁面;

八、測試地址:

http://www.yijianlan.com:8001/   ---------------------->先登陸,用戶名 test密碼 123456 能夠調用調試的接口 而後訪問看看,其餘的js 調用, 其餘平臺的我沒有試過,還不知道問題,歡迎指教!

http://www.yijianlan.com:8001/api/AreaOp/GetProvinceByName?name=省全稱   -------->    查看某個省市的全部子集

http://www.yijianlan.com:8001/api/AreaOp/GetAllAreas -------------------------------------------->  獲取所有區域(全國首位省市縣)

http://www.yijianlan.com:8001/api/Cnblog/GetAllArtic -----------------------------------------------> 獲取博客園數據(這是之前爬蟲抓的有2年了吧),隨機一百條

http://www.yijianlan.com:8001/api/Cnblog/GetArticByName?name=標題 --------------------->  查詢數據按標題

相關文章
相關標籤/搜索