1. 程式人生 > >使用ASP.NET Identity實現基於宣告的授權,高階篇

使用ASP.NET Identity實現基於宣告的授權,高階篇

在這篇文章中,我將繼續ASP.NET Identity 之旅,這也是ASP.NET Identity 三部曲的最後一篇。在本文中,將為大家介紹ASP.NET Identity 的高階功能,它支援宣告式並且還可以靈活的與ASP.NET MVC 授權結合使用,同時,它還支援使用第三方來實現身份驗證。

關於ASP.NET Identity 的基礎知識,請參考如下文章:

本文的示例,你可以在此下載和預覽:

回到頂部

走進宣告的世界

在舊的使用者管理系統,例如使用了ASP.NET Membership的應用程式,我們的應用程式被認為是獲取使用者所有資訊的權威來源,所以本質上可以將應用程式視為封閉的系統,它包含了所有的使用者資訊。在上一篇文章中,我使用ASP.NET Identity 驗證使用者儲存在資料庫的憑據,並根據與這些憑據相關聯的角色進行授權訪問,所以本質上身份驗證和授權所需要的使用者資訊來源於我們的應用程式。

ASP.NET Identity 還支援使用宣告來和使用者打交道,它效果很好,而且應用程式並不是使用者資訊的唯一來源,有可能來自外部,這比傳統角色授權來的更為靈活和方便。

接下來我將為大家介紹ASP.NET Identity 是如何支援基於宣告的授權(claims-based authorization)。

1.理解什麼是宣告

宣告(Claims)其實就是使用者相關的一條一條資訊的描述,這些資訊包括使用者的身份(如Name、Email、Country等)和角色成員,而且,它描述了這些資訊的型別、值以及釋出宣告的認證方等。我們可以使用宣告來實現基於宣告的授權。宣告可以從外部系統獲得,當然也可以從本地使用者資料庫獲取。

對於ASP.NET MVC應用程式,通過自定義AuthorizeAttribute,宣告能夠被靈活的用來對指定的Action 方法授權訪問,不像傳統的使用角色授權那麼單一,基於宣告的授權更加豐富和靈活,它允許使用使用者資訊來驅動授權訪問。

既然宣告(Claim)是一條關於使用者資訊的描述,最簡單的方式來闡述什麼是宣告就是通過具體的例子來展示,這比抽象概念的講解來的更有用。所以,我在示例專案中添加了一個名為Claims 的 Controller,它的定義如下所示:

  1. public class ClaimsController : Controller
  2. {
  3.     [Authorize]
  4.     public
     ActionResult Index()
  5.     {
  6.         ClaimsIdentity claimsIdentity = HttpContext.User.Identity as ClaimsIdentity;
  7.         if (claimsIdentity == null)
  8.         {
  9.             return View("Error", new string[] {"未找到宣告"});
  10.         }
  11.         else
  12.         {
  13.             return View(claimsIdentity.Claims);
  14.         }
  15.     }
  16. }

在這個例子中可以看出ASP.NET Identity 已經很好的整合到ASP.NET 平臺中,而HttpContext.User.Identity 屬性返回一個 IIdentity 介面的實現,而當與ASP.NET Identity 結合使用時,返回的是ClaimsIdentity 物件。

ClaimsIdentity 類被定義在System.Security.Claims 名稱空間下,它包含如下重要的成員:

Claims

返回使用者包含的宣告物件集合

AddClaim(claim)

為使用者新增一個宣告

AddClaims(claims)

為使用者新增一系列宣告

HasClaim(predicate)

判斷是否包含宣告,如果是,返回True

RemoveClaim(claim)

為使用者移除宣告

當然ClaimsIdentity 類還有更多的成員,但上述表描述的是在Web應用程式中使用頻率很高的成員。在上述程式碼中,將HttpContext.User.Identity 轉換為ClaimsIdentity 物件,並通過該物件的Claims 屬性獲取到使用者相關的所有宣告。

一個宣告物件代表了使用者的一條單獨的資訊資料,宣告物件包含如下屬性:

Issuer

返回提供宣告的認證方名稱

Subject

返回宣告指向的ClaimIdentity 物件

Type

返回宣告代表的資訊型別

Value

返回宣告代表的使用者資訊的值

有了對宣告的基本概念,對上述程式碼的View進行修改,它呈現使用者所有宣告資訊,相應的檢視程式碼如下所示:

  1. @using System.Security.Claims
  2. @using Users.Infrastructure
  3. @model IEnumerable<Claim>
  4. @{
  5.     ViewBag.Title = "Index";
  6. }
  7. <div class="panel panel-primary">
  8.     <div class="panel-heading">
  9.         聲
  10.     </div>
  11.     <table class="table table-striped">
  12.         <tr>
  13.             <th>Subject</th>
  14.             <th>Issuer</th>
  15.             <th>Type</th>
  16.             <th>Value</th>
  17.         </tr>
  18.         @foreach (Claim claim in Model.OrderBy(x=>x.Type))
  19.         {
  20.             <tr>
  21.                 <td>@claim.Subject.Name</td>
  22.                 <td>@claim.Issuer</td>
  23.                 <td>@Html.ClaimType(claim.Type)</td>
  24.                 <td>@claim.Value</td>
  25.             </tr>
  26.         }
  27.     </table>
  28. </div>

Claim物件的Type屬性返回URI Schema,這對於我們來說並不是特別有用,常見的被用來當作值的Schema定義在System.Security.Claims.ClaimType 類中,所以要使輸出的內容可讀性更強,我添加了一個HTML helper,它用來格式化Claim.Type 的值:

  1. public static MvcHtmlString ClaimType(this HtmlHelper html, string claimType)
  2. {
  3.     FieldInfo[] fields = typeof(ClaimTypes).GetFields();
  4.     foreach (FieldInfo field in fields)
  5.     {
  6.         if (field.GetValue(null).ToString() == claimType)
  7.         {
  8.             return new MvcHtmlString(field.Name);
  9.         }
  10.     }
  11.     return new MvcHtmlString(string.Format("{0}",
  12.     claimType.Split('/', '.').Last()));
  13. }

有了上述的基礎設施程式碼後,我請求ClaimsController 下的Index Action時,顯示使用者關聯的所有宣告,如下所示:

回到頂部

建立並使用宣告

有兩個原因讓我覺得宣告很有趣。第一個原因是,應用程式能從多個來源獲取宣告,而不是僅僅依靠本地資料庫來獲取。在稍後,我會向你展示如何使用外部第三方系統來驗證使用者身份和建立宣告,但此時我新增一個類,來模擬一個內部提供宣告的系統,將它命名為LocationClaimsProvider,如下所示:

  1. public static class LocationClaimsProvider
  2. {
  3.     public static IEnumerable<Claim> GetClaims(ClaimsIdentity user)
  4.     {
  5.         List<Claim> claims=new List<Claim>();
  6.         if (user.Name.ToLower()=="admin")
  7.         {
  8.             claims.Add(CreateClaim(ClaimTypes.PostalCode, "DC 20500"));
  9.             claims.Add(CreateClaim(ClaimTypes.StateOrProvince, "DC"));
  10.         }
  11.         else
  12.         {
  13.             claims.Add(CreateClaim(ClaimTypes.PostalCode, "NY 10036"));
  14.             claims.Add(CreateClaim(ClaimTypes.StateOrProvince, "NY"));
  15.         }
  16.         return claims;
  17.     }
  18.     private static Claim CreateClaim(string type,string value)
  19.     {
  20.         return new Claim(type, value, ClaimValueTypes.String, "RemoteClaims");
  21.     }
  22. }

上述程式碼中,GetClaims 方法接受一個引數為ClaimsIdentity 物件併為使用者建立了PostalCode和StateOrProvince的宣告。在這個類中,假設我模擬一個系統,如一箇中央的人力資源資料庫,那麼這將是關於工作人員本地資訊的權威來源。

宣告是在身份驗證過程被新增到使用者中,故在Account/Login Action對程式碼稍作修改:

  1. [HttpPost]
  2. [AllowAnonymous]
  3. [ValidateAntiForgeryToken]
  4. public async Task<ActionResult> Login(LoginModel model,string returnUrl)
  5. {
  6.     if (ModelState.IsValid)
  7.     {
  8.         AppUser user = await UserManager.FindAsync(model.Name, model.Password);
  9.         if (user==null)
  10.         {
  11.             ModelState.AddModelError("","無效的使用者名稱或密碼");
  12.         }
  13.         else
  14.         {
  15.             var claimsIdentity =
  16.                 await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
  17.             claimsIdentity.AddClaims(LocationClaimsProvider.GetClaims(claimsIdentity));
  18.             AuthManager.SignOut();
  19.             AuthManager.SignIn(new AuthenticationProperties {IsPersistent = false}, claimsIdentity);
  20.             return Redirect(returnUrl);
  21.         }
  22.     }
  23.     ViewBag.returnUrl = returnUrl;
  24.     return View(model);
  25. }

修改完畢,執行應用程式,身份驗證成功過後,瀏覽Claims/Index 地址,你就可以看到已經成功對使用者新增聲明瞭,如下截圖所示:

獲取宣告來自多個來源意味著我們的應用程式不會有重複資料並可以和外部資料整合。Claim 物件的Issuer 屬性 告訴你這個宣告的來源,這能幫助我們精確判斷資料的來源。舉個例子,從中央人力資源資料庫獲取的資訊比從外部供應商郵件列表獲取的資訊會更準確。

宣告是有趣的第二個原因是你能用他們來管理使用者訪問,這比使用標準的角色控制來的更為靈活。在前一篇文章中,我建立了一個專門負責角色的管理RoleContoller,在RoleController裡實現使用者和角色的繫結,一旦使用者被賦予了角色,則該成員將一直隸屬於這個角色直到他被移除掉。這會有一個潛在的問題,在大公司工作時間很長的員工,當他們換部門時換工作時,如果舊的角色沒被刪除,那麼可能會出現資料洩露的風險。

考慮使用宣告吧,如果把傳統的角色控制視為靜態的話,那麼宣告是動態的,我們可以在程式執行時動態建立宣告。宣告可以直接基於已知的使用者資訊來授權使用者訪問,這樣確保當宣告資料更改時授權也更改。

最簡單的是使用Role 宣告來對Action 受限訪問,這我們已經很熟悉了,因為ASP.NET Identity 已經很好的整合到了ASP.NET 平臺中了,當使用ASP.NET Identity 時,HttpContext.User 返回的是ClaimsPrincipal 物件,它實現了IsInRole 方法並使用HasClaim來判斷指定的角色宣告是否存在,從而達到授權。

接著剛才的話題,我們想讓授權是動態的,是由使用者資訊(宣告)驅動的,所以我建立了一個ClaimsRoles類,用來模擬生成宣告,如下所示:

  1. public class ClaimsRoles
  2. {
  3.     public static IEnumerable<Claim> CreateRolesFromClaims(ClaimsIdentity user)
  4.     {
  5.         List<Claim> claims = new List<Claim>();
  6.         if (user.HasClaim(x => x.Type == ClaimTypes.StateOrProvince
  7.         && x.Issuer == "RemoteClaims" && x.Value == "北京")
  8.         && user.HasClaim(x => x.Type == ClaimTypes.Role
  9.         && x.Value == "Employee"))
  10.         {
  11.             claims.Add(new Claim(ClaimTypes.Role, "BjStaff"));
  12.         }
  13.         return claims;
  14.     }
  15. }

初略看一下CreateRolesFromClaims方法中的程式碼,使用Lambda表示式檢查使用者是否有來自Issuer為RemoteClaims ,值為北京的StateOrProvince宣告和值為Employee 的Role宣告,如果使用者都包含兩者,新增一個值為BjStaff 的 Role 宣告。最後在Login Action 時呼叫此方法,如下所示:

  1. [HttpPost]
  2. [AllowAnonymous]
  3. [ValidateAntiForgeryToken]
  4. public async Task<ActionResult> Login(LoginModel model,string returnUrl)
  5. {
  6.     if (ModelState.IsValid)
  7.     {
  8.         AppUser user = await UserManager.FindAsync(model.Name, model.Password);
  9.         if (user==null)
  10.         {
  11.             ModelState.AddModelError("","無效的使用者名稱或密碼");
  12.         }
  13.         else
  14.         {
  15.             var claimsIdentity = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
  16.             claimsIdentity.AddClaims(LocationClaimsProvider.GetClaims(claimsIdentity));
  17.             claimsIdentity.AddClaims(ClaimsRoles.CreateRolesFromClaims(claimsIdentity));
  18.             AuthManager.SignOut();
  19.             AuthManager.SignIn(new AuthenticationProperties {IsPersistent = false}, claimsIdentity);
  20.             return Redirect(returnUrl);
  21.         }
  22.     }
  23.     ViewBag.returnUrl = returnUrl;
  24.     return View(model);
  25. }

現在就可以基於角色為BjStaff對OtherAction受限訪問,如下所示:

  1. [Authorize(Roles = "BjStaff")]
  2. public string OtherAction()
  3. {
  4.     return "這是一個受保護的Action";
  5. }

當用戶資訊發生改變時,如若生成的宣告不為BjStaff,那麼他也就沒許可權訪問OtherAction了,這完全是由使用者資訊所驅動,而非像傳統的在RoleController中顯示修改使用者和角色的關係。

回到頂部

基於宣告的授權

在前一個例子中證明了如何使用宣告來授權,但是這有點不直接因為我基於宣告來產生角色然後再基於新的角色來授權。一個更加直接和靈活的方法是通過建立一個自定義的授權過濾器特性來實現,如下展示:

  1. public class ClaimsAccessAttribute:AuthorizeAttribute
  2. {
  3.     public string Issuer { getset; }
  4.     public string ClaimType { getset; }
  5.     public string Value { getset; }
  6.     protected override bool AuthorizeCore(HttpContextBase context)
  7.     {
  8.         return context.User.Identity.IsAuthenticated
  9.         && context.User.Identity is ClaimsIdentity
  10.         && ((ClaimsIdentity)context.User.Identity).HasClaim(x =>
  11.         x.Issuer == Issuer && x.Type == ClaimType && x.Value == Value
  12.         );
  13.     }
  14. }

ClaimsAccessAttribute 特性繼承自AuthorizeAttribute,並Override了 AuthorizeCore 方法,裡面的業務邏輯是當用戶驗證成功並且IIdentity的實現是ClaimsIdentity 物件,同時使用者包含通過屬性傳入的宣告,最後將此Attribute 放在AnOtherAction 前,如下所示:

  1.  [ClaimsAccess(Issuer = "RemoteClaims", ClaimType = ClaimTypes.PostalCode, Value = "200000")]
  2. public string AnotherAction()
  3. {
  4.     return "這也是一個受保護的Action";
  5. }
回到頂部

使用第三方來身份驗證

像ASP.NET Identity 這類基於宣告的系統的一個好處是任何宣告能從外部系統獲取,這意味著其他應用程式能幫我們來身份驗證。ASP.NET Identity 基於這個原則增加對第三方如Google、Microsoft、FaceBook身份驗證的支援。

使用第三方身份驗證有許多好處:許多使用者已經有一個第三方賬戶了,並且你也不想在這個應用程式管理你的憑據。使用者也不想在每一個網站上註冊賬戶並都記住密碼。使用一個統一的賬戶會比較靈活。

1.啟用Google 賬戶身份驗證

ASP.NET Identity 釋出了對第三方身份驗證的支援,通過Nuget來安裝:

Install-Package Microsoft.Owin.Security.Google

當Package 安裝完成後,在OWIN Startup啟動項中,新增對身份驗證服務的支援:

  1. app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
  2. //http://www.asp.net/mvc/overview/security/create-an-aspnet-mvc-5-app-with-facebook-and-google-oauth2-and-openid-sign-on
  3. app.UseGoogleAuthentication(new GoogleOAuth2AuthenticationOptions()
  4. {
  5.     ClientId = "165066370005-6nhsp87llelff3tou91hhktg6eqgr0ke.apps.googleusercontent.com",
  6.     ClientSecret = "euWbCSUZujjQGKMqOyz0msbq",
  7. });

在View中,新增一個通過Google 登陸的按鈕:

  1. 相關推薦

    使用ASP.NET Identity實現基於宣告授權高階

    在這篇文章中,我將繼續ASP.NET Identity 之旅,這也是ASP.NET Identity 三部曲的最後一篇。在本文中,將為大家介紹ASP.NET Identity 的高階功能,它支援宣告式並且還可以靈活的與ASP.NET MVC 授權結合使用,同時,它還支援使用第

    ASP.NET MVC 隨想錄—— 使用ASP.NET Identity實現基於宣告授權高階

    在這篇文章中,我將繼續ASP.NET Identity 之旅,這也是ASP.NET Identity 三部曲的最後一篇。在本文中,將為大家介紹ASP.NET Identity 的高階功能,它支援宣告式並且還可以靈活的與ASP.NET MVC 授權結合使用,同時,它還支援使用第三方來實現身份驗證。 關於A

    探索ASP.NET Identity 身份驗證和基於角色的授權中級

    在前一篇文章中,我介紹了ASP.NET Identity 基本API的運用並建立了若干使用者賬號。那麼在本篇文章中,我將繼續ASP.NET Identity 之旅,向您展示如何運用ASP.NET Identity 進行身份驗證(Authentication)以及聯合ASP.N

    Asp.Net.Identity認證不依賴Entity Framework實現方式

    aps 新建 create exc spn sharp 個數 blank aspnet Asp.Net.Identity為何物請自行搜索,也可轉向此文章http://www.cnblogs.com/shanyou/p/3918178.html 本來微軟已經幫我們將授權、認證

    Asp.net Identity 修改默認數據庫增加自定義字段

    擴展 studio required ssa 字段 profile 服務器 cat fix visual studio 2013 先新建一個項目 選擇MVC,確定 打開 Views\Shared\_Layout.cshtml文件,按自己的要求修改 改 [ht

    NoSql-MongoDB GridFS+ASP.NET MVC實現上傳顯示

    namespace MongoDBTest.Controllers { public class MongoDBHelperController : Controller { private static MongoDatabase DB; public sta

    開始使用ASP.NET Identity初級

    在之前的文章中,我為大家介紹了OWIN和Katana,有了對它們的基本瞭解後,才能更好的去學習ASP.NET Identity,因為它已經對OWIN 有了良好的整合。在這篇文章中,我主要關注ASP.NET Identity的建立和使用,包括基礎類的搭建和使用者管理功能的實現—

    Asp.net 如何實現微信公眾號授權登入

    第一個類:封裝好微信配置檔案 using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Web; using Newton

    【無私分享:從入門到精通ASP.NET MVC】從0開始一起搭框架、做專案(5.3) 登入功能的實現豐富資料表、建立關聯

    1 USE [wkmvc_db] 2 GO 3 /****** Object: Table [dbo].[SYS_CODE] Script Date: 2016/5/17 9:30:01 ******/ 4 SET ANSI_NULLS ON 5 GO 6 SET

    【無私分享:從入門到精通ASP.NET MVC】從0開始一起搭框架、做專案(5.4) 登入功能的實現建立與登入使用者相關的介面和實現

    索引 簡述 今天我們建立幾個與登入使用者相關的資料表的介面和實現類 專案準備 我們用的工具是:VS 2013 + SqlServer 2012 + IIS7.5 希望大家對ASP.NET MVC有一個初步的理解,理論性的東西我們不做過多解釋,有些地方不理解也沒關係,會用就行了,用的多了,用的久了

    【無私分享:從入門到精通ASP.NET MVC】從0開始一起搭框架、做專案(5.5) 登入功能的實現完善登入功能

    索引 簡述 今天我們來完善我們的登入功能 專案準備 我們用的工具是:VS 2013 + SqlServer 2012 + IIS7.5 希望大家對ASP.NET MVC有一個初步的理解,理論性的東西我們不做過多解釋,有些地方不理解也沒關係,會用就行了,用的多了,用的久了,自然就理解了。 專案開

    【無私分享:從入門到精通ASP.NET MVC】從0開始一起搭框架、做專案(5.2) 登入功能的實現介面注入、log4net的使用

    索引 簡述 前兩天事情比較多,耽誤更新了,希望大家多多包涵,今天我們繼續做我們的登入功能 專案準備 我們用的工具是:VS 2013 + SqlServer 2012 + IIS7.5 希望大家對ASP.NET MVC有一個初步的理解,理論性的東西我們不做過多解釋,有些地方不理解也沒關係,會用就行

    【無私分享:ASP.NET CORE 專案實戰(第十三章)】Asp.net Core 使用MyCat分散式資料庫實現讀寫分離

    目錄索引 簡介   MyCat2.0版本很快就釋出了,關於MyCat的動態和一些問題,大家可以加一下MyCat的官方QQ群:106088787。我們今天主要介紹一下,在我們的Asp.net Core中如何使用Mycat,這源於一個大神(Amamiya Yuuko)的分享,但是,這中

    【無私分享:從入門到精通ASP.NET MVC】從0開始一起搭框架、做專案(5.1) 登入功能的實現開始接觸Spring IOC、DI

    索引 簡述 今天我們做登入,今天的東西比較多,用到了Spring的IOC和DI、介面的使用、驗證等,希望大家多多討論 專案準備 我們用的工具是:VS 2013 + SqlServer 2012 + IIS7.5 希望大家對ASP.NET MVC有一個初步的理解,理論性的東西我們不做過多解釋,有些

    ASP.NET MVC 隨想錄——開始使用ASP.NET Identity初級

    在之前的文章中,我為大家介紹了OWIN和Katana,有了對它們的基本瞭解後,才能更好的去學習ASP.NET Identity,因為它已經對OWIN 有了良好的整合。 在這篇文章中,我主要關注ASP.NET Identity的建立和使用,包括基礎類的搭建和使用者管理功能的實現—— 在後續文章中,我將

    第7章 成員資格、授權(Authorize、ASP.NET Identity、OAuth和OpenID的外部登入)和安全性

    7.1 安全性:無趣、但極其重要7.2 使用Authorize特性登入       使用Authorize特性來阻止使用者匿名訪問控制器或控制器操作。7.2.1 保護控制器操作情況1:單控制器             控制器上新增 [Authorize]特性情況2: 全部控制

    基於layui+asp.net mvc實現個人部落格系統

    功能如下: 後臺: 1.文章管理 2.分類管理 3.設定 4.日誌管理 前臺 1.顯示後臺釋出的文章資訊 2.評論功能 專案類圖: 放大檢視 ArticleController.cs using WuBlog.

    譯:Asp.Net Identity與Owin到底誰是誰?

    送給正在學習Asp.Net Identity的你 :-) Recently I have found an excellent question on Stackoverflow. The OP asks why does claim added to Ideneti

    ASP.NET Core 中基於策略的授權

    軟體應用程式的授權層可確保當前使用者能夠訪問指定資源、執行給定操作或對指定資源執行給定操作。在 ASP.NET Core 中,授權層的設定方式有兩種。可以使用角色,也可以使用策略。前一種方法(即基於角色的授權)一直在舊版 ASP.NET 平臺中沿用,而基於策略的

    ASP.NET Core實現強類型Configuration讀取配置數據

    控制器 項目 最好 前言實現讀取JSON文件幾種方式,在項目中采取老辦法簡單粗暴,結果老大過來一看,恩,這樣不太可取,行吧那我就用.NET Core中最新的方式諾,切記,適合的才是最好的,切勿懶。.NET Core讀取JSON文件通過讀取文件方式 當我將VS2015項目用VS2017打開後