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
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...