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

来源:https://www.cnblogs.com/w5942066/archive/2019/12/17/12055542.html
-Advertisement-
Play Games

1、前言: WebAPI主要開放數據給手機APP,Pad,其他需要得知數據的系統,或者軟體應用。Web 用戶的身份驗證,及頁面操作許可權驗證是B/S系統的基礎功能。我上次寫的《Asp.Net MVC WebAPI的創建與前臺Jquery ajax後臺HttpClient調用詳解》這種跟明顯安全性不是那 ...


1、前言:

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

2、新建一個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緩存的一致這樣判斷的

3、在建一個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");
        }

    }
}

4、在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);
        }
    }

 

 

5、現在來寫一個登陸,新建一個控制器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 = "登錄成功!" });
        }
    }

 

登陸頁面 簡單而粗暴

 

<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數據了

 

 

 

 6、現在就可以寫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);

現在看效果圖

 

 

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

 

 

效果都是一樣的,如果登錄了就可以直接訪問 了 不用加參數 ,只有方法需要參數的就可以加 

 

 

 

 

 

 這裡貼一個調用的代碼:

 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;
            }

 

7、總結:

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

2) 如果沒有票據信息,或者票據信息不是合法的,則返回“未授權的訪問”異常消息給前端,由前端處理此異常。

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

4)、當用戶有許可權操作頁面或頁面元素時,跳轉到頁面,並由頁面Controller提交業務數據處理請求到api伺服器; 如果用戶沒有許可權訪問該頁面或頁面元素時,則顯示“未授權的訪問操作”,跳轉到系統異常處理頁面。

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

8、測試地址:

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=標題 --------------------->  查詢數據按標題

 

 

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 看此文檔前,先參考一下文檔 https://blog.csdn.net/downmoon/article/details/24374609 環境:阿裡雲ECS SQL Server 2017 + Delphi7 測試用xcopy,robocopy等命令遷移文件好像不太會用。有感興趣的朋友,告知一下。 ...
  • property property是一種特殊的屬性,訪問它時會執行一段功能(函數)然後返回值;就是把一個函數屬性的訪問方式變成像訪問數據屬性的方式一樣。 我們首先來看一個對比效果 例一:在調用 bmi 函數的時候需要加括弧的,可是我們往往需要另一種調用方法——不想加括弧 class people() ...
  • 一 跨域 同源策略(Same origin policy)是一種約定,它是瀏覽器最核心也最基本的安全功能,如果缺少了同源策略,則瀏覽器的正常功能可能都會受到影響。可以說Web是構建在同源策略基礎之上的,瀏覽器只是針對同源策略的一種實現。 同源策略,它是由Netscape提出的一個著名的安全策略。現在 ...
  • 在Web滲透流程的暴力登錄場景和爬蟲抓取場景中,經常會遇到一些登錄表單用DES之類的加密方式來加密參數,也就是說,你不搞定這些前端加密,你的編寫的腳本是不可能Login成功的。針對這個問題,現在有三種解決方式: ①看懂前端的加密流程,然後用腳本編寫這些方法(或者找開源的源碼),模擬這個加密的流程。缺 ...
  • 登錄系統(註意這裡啟動 tomcat 的用戶) 使用 MAT 分析 下載 dump.hprof ,使用 MAT 打開分析 ...
  • 通常系統都會限制同一個賬號的登錄人數,多人登錄要麼限制後者登錄,要麼踢出前者,Spring Security 提供了這樣的功能,本文講解一下在沒有使用Security的時候如何手動實現這個功能 demo 技術選型 SpringBoot JWT Filter Redis + Redisson JWT( ...
  • 一、概述通常來說,“行為請求者”與“行為實現者”是緊耦合的。但在某些場合,比如要對行為進行“記錄、撤銷/重做、事務”等處理,這種無法抵禦變化的緊耦合是不合適的。在這些情況下,將“行為請求者”與“行為實現者”解耦,實現二者之間的松耦合就至關重要。命令模式是解決這類問題的一個比較好的方法。二、命令模式命 ...
  • 環境:VS2019 .net 4.0 framework 根據教材使用ScriptManager在JavaScript中調用Web service 時,失敗。現將過程和解決方法記錄如下: 1、定義Web Service 2、定義JavaScript和.aspx頁面; 整個項目的目錄如下: 3、運行程 ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...