為了防止不提供原網址的轉載,特在這裡加上原文鏈接: "http://www.cnblogs.com/skabyy/p/7695258.html" 本篇將實現登錄、許可權控制、日誌配置與審計日誌的功能。首先我們先實現登錄功能,在登錄的基礎上,通過控權使得只有ID為1988的用戶才能創建tweet。最後配 ...
為了防止不提供原網址的轉載,特在這裡加上原文鏈接:
http://www.cnblogs.com/skabyy/p/7695258.html
本篇將實現登錄、許可權控制、日誌配置與審計日誌的功能。首先我們先實現登錄功能,在登錄的基礎上,通過控權使得只有ID為1988的用戶才能創建tweet。最後配置Log4Net日誌,並開啟審計日誌,記錄所有Web請求。
簡單的界面
為了測試方便,在實現登錄功能之前,先簡單實現了幾個頁面:
Tweets列表頁面
創建tweet頁面
登錄頁面
頁面代碼沒有什麼特別的,這裡就不贅述了。
登錄
我們不希望所有人都能創建tweet,而是只有已登錄的用戶才能創建。本小節將實現登錄功能,限制創建tweet頁面只有已登陸用戶才能訪問。
首先在Web.config
的system.web
裡加上這段配置:
然後設置首頁和登錄頁面可以匿名訪問。給Home/Index
和Account/Login
這兩個Action加上AllowAnonymous
特性。
[AllowAnonymous]
public ActionResult Index()
[AllowAnonymous]
public ActionResult Login(string returnUrl)
接下來實現登錄功能。登錄功能的實現有兩步:
用戶發起登錄請求後,驗證完用戶名密碼,生成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
特性允許匿名訪問。為了測試方便,這裡沒有對用戶名密碼進行驗證,使用任意用戶名任意密碼都能登錄。用戶每次訪問時,根據
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能夠使用登錄用戶信息,要將
Type
為AbpClaimTypes.UserId
,值為userId
的Claim
添加到Identity
里,這樣userId
會自動註入到AbpSession
中。我們在後續代碼中也可以通過AbpSession.UserId.HasValue
來判斷用戶是否已登陸。需要註意的一點是ABP只支持數字類型的
userId
。所以要確保userId
是一個能轉成整數的字元串。如果需要其他類型的userId
(比如字元串類型)則需要對AbpSession
進行擴展。
許可權控制
本小節將對創建tweet的許可權做進一步的限制,讓只有ID為1988的用戶才可以創建tweet。為了實現許可權控制,我們需要實現三個部分:
定義許可權。
新建類
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); } }
許可權判斷邏輯。即哪些用戶擁有哪些許可權的邏輯。
通過實現介面
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
了。標記哪些方法(一般是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>
創建好配置文件後,到MvcApplication
的Application_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的源碼。為了弄清楚一些犄角旮旯的細節,在文檔里翻找半天往往不如直接查閱代碼來得效率高。