爲了防止不提供原網址的轉載,特在這裏加上原文連接:
http://www.cnblogs.com/skabyy/p/7695258.htmlhtml
本篇將實現登陸、權限控制、日誌配置與審計日誌的功能。首先咱們先實現登陸功能,在登陸的基礎上,經過控權使得只有ID爲1988的用戶才能建立tweet。最後配置Log4Net日誌,並開啓審計日誌,記錄全部Web請求。前端
爲了測試方便,在實現登陸功能以前,先簡單實現了幾個頁面:git
Tweets列表頁面github
建立tweet頁面web
登陸頁面數據庫
頁面代碼沒有什麼特別的,這裏就不贅述了。安全
咱們不但願全部人都能建立tweet,而是隻有已登陸的用戶才能建立。本小節將實現登陸功能,限制建立tweet頁面只有已登錄用戶才能訪問。cookie
首先在Web.config
的system.web
里加上這段配置:app
而後設置首頁和登陸頁面能夠匿名訪問。給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的源碼。爲了弄清楚一些犄角旮旯的細節,在文檔裏翻找半天每每不如直接查閱代碼來得效率高。