手工搭建基於ABP的框架(3) - 登錄,許可權控制與日誌

来源:http://www.cnblogs.com/skabyy/archive/2017/11/20/7695258.html
-Advertisement-
Play Games

為了防止不提供原網址的轉載,特在這裡加上原文鏈接: "http://www.cnblogs.com/skabyy/p/7695258.html" 本篇將實現登錄、許可權控制、日誌配置與審計日誌的功能。首先我們先實現登錄功能,在登錄的基礎上,通過控權使得只有ID為1988的用戶才能創建tweet。最後配 ...


為了防止不提供原網址的轉載,特在這裡加上原文鏈接:
http://www.cnblogs.com/skabyy/p/7695258.html

本篇將實現登錄、許可權控制、日誌配置與審計日誌的功能。首先我們先實現登錄功能,在登錄的基礎上,通過控權使得只有ID為1988的用戶才能創建tweet。最後配置Log4Net日誌,並開啟審計日誌,記錄所有Web請求。

簡單的界面

為了測試方便,在實現登錄功能之前,先簡單實現了幾個頁面:

  1. Tweets列表頁面

  2. 創建tweet頁面

  3. 登錄頁面

頁面代碼沒有什麼特別的,這裡就不贅述了。

登錄

我們不希望所有人都能創建tweet,而是只有已登錄的用戶才能創建。本小節將實現登錄功能,限制創建tweet頁面只有已登陸用戶才能訪問。

首先在Web.configsystem.web裡加上這段配置:

然後設置首頁和登錄頁面可以匿名訪問。給Home/IndexAccount/Login這兩個Action加上AllowAnonymous特性。

[AllowAnonymous]
public ActionResult Index()

[AllowAnonymous]
public ActionResult Login(string returnUrl)

接下來實現登錄功能。登錄功能的實現有兩步:

  1. 用戶發起登錄請求後,驗證完用戶名密碼,生成cookie,然後把cookie返回給前端。

    [HttpPost]
    [AllowAnonymous]
    public ActionResult LoginAjax(LoginInput input)
    {
        // 這裡應該放驗證用戶名密碼是否正確的代碼。
        // 為了測試方便,這裡跳過驗證,使用任意用戶名任意密碼都能登錄。
        var username = input.Username;
        var ticket = new FormsAuthenticationTicket(
            1 /* version */,
            username,
            DateTime.Now,
            DateTime.Now.Add(FormsAuthentication.Timeout),
            false /* persistCookie */,
            "" /* userData */);
        var userCookie = new HttpCookie(
            FormsAuthentication.FormsCookieName,
            FormsAuthentication.Encrypt(ticket));
        HttpContext.Response.Cookies.Add(userCookie);
        return Json("OK");
    }

    註意LoginAjax介面要加上AllowAnonymous特性允許匿名訪問。為了測試方便,這裡沒有對用戶名密碼進行驗證,使用任意用戶名任意密碼都能登錄。

  2. 用戶每次訪問時,根據userId創建claim、identity和principal,並把principal賦值給HttpContext.Current.User。這部分代碼實現在過濾器。新建過濾器類MvcAuthorizeAttribute

    public class MvcAuthorizeAttribute : AuthorizeAttribute
    {
        public override void OnAuthorization(AuthorizationContext filterContext)
        {
            // IIS會從cookie解析出userId並生成一個principal賦值給Thread.CurrentPrincipal
            var userId = Thread.CurrentPrincipal?.Identity?.Name;
    
            if (!string.IsNullOrEmpty(userId))
            {
                // 創建identity
                var identity = new GenericIdentity(userId);
                // 添加Type為AbpClaimTypes.UserId使userId能註入到AbpSession
                identity.AddClaim(new Claim(AbpClaimTypes.UserId, userId));
                // 創建principal
                var principal = new GenericPrincipal(identity, null);
                // 同步Thread.CurrentPrincipal
                Thread.CurrentPrincipal = principal;
                if (HttpContext.Current != null)
                {
                    // 將principal賦值給HttpContext.Current.User,用戶就登錄進去了。
                    HttpContext.Current.User = principal;
                }
            }
    
            base.OnAuthorization(filterContext);
        }
    }

    關於claim、identity和principal這三個概念的詳細解釋可以看這位哥們的博客。然後將過濾器MvcAuthorizeAttribute加到全局過濾器配置:

    filters.Add(new MvcAuthorizeAttribute());

    註意到過濾器MvcAuthorizeAttribute繼承了AuthorizeAttribute。因而將這個過濾器加到全局過濾後,除了帶AllowAnonymous特性的Action外,其他Action被未登錄用戶訪問時就會跳轉到登錄頁面。

    另外為了讓ABP能夠使用登錄用戶信息,要將TypeAbpClaimTypes.UserId,值為userIdClaim添加到Identity里,這樣userId會自動註入到AbpSession中。我們在後續代碼中也可以通過AbpSession.UserId.HasValue來判斷用戶是否已登陸。

    需要註意的一點是ABP只支持數字類型的userId。所以要確保userId是一個能轉成整數的字元串。如果需要其他類型的userId(比如字元串類型)則需要AbpSession進行擴展

許可權控制

本小節將對創建tweet的許可權做進一步的限制,讓只有ID為1988的用戶才可以創建tweet。為了實現許可權控制,我們需要實現三個部分:

  1. 定義許可權。

    新建類MyTweetAuthorizationProvider,在SetPermissions方法中定義創建tweet的許可權。MyTweetAuthorizationProvider要繼承AuthorizationProvider

    public static class MyTweetPermission
    {
        public const string CreateTweet = "CreateTweet";
    }
    
    public class MyTweetAuthorizationProvider : AuthorizationProvider
    {
        public override void SetPermissions(IPermissionDefinitionContext context)
        {
            context.CreatePermission(MyTweetPermission.CreateTweet);
        }
    }
  2. 許可權判斷邏輯。即哪些用戶擁有哪些許可權的邏輯。

    通過實現介面IPermissionChecker來實現自定義的許可權判斷邏輯。新建類MyTweetPermissionChecker,並將邏輯寫在方法IsGrantedAsync中。我們只允許ID為“1988”的用戶創建tweet。

    public class MyTweetPermissionChecker : IPermissionChecker, ITransientDependency
    {
        public IAbpSession AbpSession { get; set; }
    
        public Task<bool> IsGrantedAsync(string permissionName)
        {
            var userId = AbpSession.GetUserId();
            return IsGrantedAsync(new UserIdentifier(null, userId), permissionName);
        }
    
        public Task<bool> IsGrantedAsync(UserIdentifier user, string permissionName)
        {
            var userId = user.UserId;
            var t = new Task<bool>(() =>
            {
                if (permissionName == MyTweetPermission.CreateTweet)
                {
                    return userId == 1988;
                }
                return true;
            });
            t.Start();
            return t;
        }
    }

    這裡有兩個地方要註意。一個是類MyTweetPermissionChecker同時要實現ITransientDependency,才能被自動註入(IPermissionChecker並沒有繼承ITransientDependency)。另一個地方是方法IsGrantedAsync是非同步方法,要返回Task<bool>類型,並且確保返回的task已經Start了。

  3. 標記哪些方法(一般是Action或AppService的方法)屬於哪些許可權。

    使用AbpMvcAuthorize將創建tweet的許可權標記在Action /Home/CreateTweet上:

    [AbpMvcAuthorize(MyTweetPermission.CreateTweet)]
    public ActionResult CreateTweet()

    為了讓AbpMvcAuthorize能生效,我們還需要讓HomeController繼承AbpController(在實踐中,一般要在AbpController上再封裝一個BaseController)。

    public class HomeController : AbpController

    並且MyTweetWebModule要依賴AbpWebMvcModule

    [DependsOn(
        typeof(AbpWebMvcModule),
        typeof(AbpWebApiModule),
        typeof(MyTweetApplicationModule))]
    public class MyTweetWebModule : AbpModule

    另外,創建tweet的POST介面也要控權。由於WebAPI是取不到AbpSession的(如果一定要用WebAPI只能用其它方法控權),因此我們需要另外做一個MVC版本的介面來控權(然後前端也做相應的修改):

    public class TweetController : AbpController
    {
        private IMyTweetAppService _myTweetAppService;
    
        public TweetController(IMyTweetAppService appSvc)
        {
            _myTweetAppService = appSvc;
        }
    
        [HttpPost]
        [AbpMvcAuthorize(MyTweetPermission.CreateTweet)]
        public ActionResult Create(CreateTweetInput input)
        {
            var tweet = _myTweetAppService.CreateTweet(input);
            return Json(tweet);
        }
    }

    另外,你也可以在應用層上標記許可權(前提是你是用MVC介面調用應用層的方法,而非WebAPI)。Controller用AbpMvcAuthorize標記許可權,而AppService用AbpAuthorize標記許可權。

    [AbpAuthorize(MyTweetPermission.CreateTweet)]
    public object CreateTweet(CreateTweetInput input)

    測試一下,用“1988”登錄可以正常訪問(正常訪問的不截圖了)。而其他用戶則提示無訪問許可權:

日誌配置與審計日誌

日誌配置

ABP框架使用Log4Net來進行日誌管理,並且在Log4Net基礎上封裝了個Abp.Castle.Log4Net包。首先將NuGet包Abp.Castle.Log4Net安裝到MyTweet.Web項目。

然後在MyTweet.Web根目錄下創建Log4Net的配置文件(你也可以在其他你喜歡的位置創建,只要後面代碼里寫對路徑就行),文件名為log4net.config。下麵是我用的配置文件,基本上用的Log4Net預設的配置內容,只是日誌存放文件修改到了Logs/Logs.txt

<?xml version="1.0" encoding="utf-8" ?>
<log4net>
  <appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender" >
    <file value="Logs/Logs.txt" />
    <appendToFile value="true" />
    <rollingStyle value="Size" />
    <maxSizeRollBackups value="10" />
    <maximumFileSize value="10000KB" />
    <staticLogFileName value="true" />
    <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%-5level %date [%-5.5thread] %-40.40logger - %message%newline" />
    </layout>
  </appender>
  <root>
    <appender-ref ref="RollingFileAppender" />
    <level value="DEBUG" />
  </root>
  <logger name="NHibernate">
    <level value="WARN" />
  </logger>
</log4net>

創建好配置文件後,到MvcApplicationApplication_Start方法裡加上下麵這行代碼,開啟日誌功能。

IocManager.Instance.IocContainer.AddFacility<LoggingFacility>(
    f => f.UseAbpLog4Net().WithConfig("log4net.config"));

有個要註意的地方是需要在文件開頭加上下麵這行using語句,不然f.UseAbpLog4Net會報錯。

using Abp.Castle.Logging.Log4Net;

配置好後,我們可以使用依賴註入註入到ILogger介面的Logger對象來寫日誌。在首頁的Action方法中加上幾行寫日誌的代碼:

[AllowAnonymous]
public ActionResult Index()
{
    if (AbpSession.UserId.HasValue)
    {
        Logger.Info(string.Format("用戶{0}訪問了首頁!", AbpSession.UserId));
    }
    else
    {
        Logger.Info("匿名用戶訪問了首頁!");
    }
    return View();
}

分別用匿名身份和登錄用戶身份訪問一下首頁,然後到MyTweet.Web的根目錄下查看Logs/Logs.txt文件:

審計日誌

維基百科說: “審計跟蹤(也叫審計日誌)是與安全相關的按照時間順序的記錄,記錄集或者記錄源,它們提供了活動序列的文檔證據,這些活動序列可以在任何時間影響一個特定的操作,步驟或其他”。

對於我們Web應用來說,審計日誌負責記錄所有用戶的請求。如果是硬編碼實現的話,我們需要在所有的Action方法裡加上記錄日誌的代碼。這顯然是既耗時又不科學的。幸運的是我們不需要這麼做,ABP框架自帶審計日誌的功能。只要我們配置好日誌功能(前面已經做了),ABP會預設記錄所有已登陸用戶(通過AbpSession.UserId.HasValue判斷是否已登陸)的訪問。查看Logs/Logs.txt文件會發現剛纔我們訪問首頁的行為已經被記錄到日誌里了。

INFO  2017-11-02 15:39:23,055 [29   ] Abp.Auditing.SimpleLogAuditingStore      - AUDIT LOG: MyTweet.Web.Controllers.HomeController.Index is executed by user 1988 in 1 ms from 10.211.55.3 IP address with succeed.

這行日誌記錄了這些信息:用戶名、用戶IP地址、訪問的方法、響應耗時以及訪問結果。另外,在這行日誌的開頭有這個欄位:Abp.Auditing.SimpleLogAuditingStore。這個表示該日誌內容是由類Abp.Auditing.SimpleLogAuditingStore處理記錄的。該類實現了IAuditingStore介面。如果我們要自定義審計日誌的內容,我們需要自己實現這個介面。下麵我們實現一個輸出中文的審計日誌。在MyTweet.Web項目下新建類MyTweetLogAuditingStore

public class MyTweetLogAuditingStore : IAuditingStore, ITransientDependency
{
    public ILogger Logger { get; set; }

    public MyTweetLogAuditingStore()
    {
        Logger = NullLogger.Instance;
    }

    public Task SaveAsync(AuditInfo auditInfo)
    {
        var userId = auditInfo.UserId;
        var userIp = auditInfo.ClientIpAddress;
        var browserInfo = auditInfo.BrowserInfo;
        var action = $"{auditInfo.ServiceName}.{auditInfo.MethodName}";
        var ms = auditInfo.ExecutionDuration;
        var msg = $"用戶{userId}(坐標{userIp})使用{browserInfo}訪問了方法{action},該方法在{ms}毫秒內進行了回擊,回擊結果:";
        if (auditInfo.Exception == null)
        {
            Logger.Info(msg + "成功!");
        }
        else
        {
            Logger.Warn(msg + "出錯了:" + auditInfo.Exception.Message);
        }
        return Task.FromResult(0);
    }
}

再訪問首頁,然後看看日誌記了啥:

INFO  2017-11-02 16:45:53,374 [35   ] et.Web.App_Start.MyTweetLogAuditingStore - 用戶1988(坐標10.211.55.3)使用Chrome / 61.0 / WinNT訪問了方法MyTweet.Web.Controllers.HomeController.Index,該方法在77毫秒內進行了回擊,回擊結果:成功!

關於審計日誌的其他配置這裡不再多說,有需要的同學可以看這篇博客

總結

我們已經使用ABP搭建了一個相對完整的tweet應用。它雖然十分簡陋,但也是五臟俱全。它能夠進行資料庫訪問,擁有登錄、控權、日誌等功能。後面會再添加UoW、單元測試等內容。

關於ABP後續的學習和使用,除了查看官方文檔外,強烈建議直接閱讀ABP的源碼。為了弄清楚一些犄角旮旯的細節,在文檔里翻找半天往往不如直接查閱代碼來得效率高。


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

-Advertisement-
Play Games
更多相關文章
  • 回到目錄 在進行.net core平臺之後,我們如果希望在請求過程中添加一些事件是非常容易的,你可以把這些事件做成一個中間件Middleware,然後這些中間件就會以Http pipeline的管道方式進行相應,並且它們就像是一個職責鏈,從你定義的第一個中間件開始,一個一個向下傳遞,直到最後一個中間 ...
  • Nginx是個輕量級的反向代理,當然,也有相應的SSL身份認證。本文主要採用一種自我寄宿的方式,使用Nginx集群,通過windows證書(X.509證書),講述客戶端如何訪問伺服器的方法。客戶端以BasicHttpBinding進行訪問Nginx,然後Nginx進行負載均衡,將消息分發到後端任意一... ...
  • 1,菜單。在最新的abp3.1.2中 菜單欄是在左側的如下圖(上中下的結構),中間部分就是我們要添加菜單的地方。 ABP集成了通用的創建和顯示菜單的方式,在展示層web下的appstart文件夾下找到AbpNavigationProvider 添加如下代碼即可 關於菜單部分的具體實現原理,可以在ht ...
  • 1 軟體度量值指標 1.1 可維護性指數 表示源代碼的可維護性,數值越高可維護性越好。該值介於0到100之間。綠色評級在20到100之間,表明該代碼具有高度的可維護性;黃色評級在10到19之間,表示該代碼適度可維護;紅色評級在0至9之間,表示低可維護性。 1.2 圈複雜度 它是通過計算程式流中不同代 ...
  • 當我們使用QQ的時候就會發現,他可以啟動多個QQ,但是有時候,我們不想這樣做,這時候我們就需要使用到單例模式. 1.將Form2的構造函數轉為私有 using System.Windows.Forms; namespace 單例模式 { public partial class Form2 : Fo ...
  • 演示產品下載地址:http://www.jinhusns.com ...
  • 下載地址:http://ilspy.net/ 中文版下載地址:http://www.fishlee.net/soft/ilspy_chs 對dll和exe文件反編譯: ...
  • 在Winform開發中,我們往往除了常規的單表信息錄入外,有時候設計到多個主從表的數據顯示、編輯等界面,單表的信息一般就是控制項和對象實體一一對應,然後調用API保存即可,主從表就需要另外特殊處理,本隨筆介紹如何快速實現主從表編輯界面的處理,結合GridControl控制項的GridView控制項對象,實... ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...