此篇紀錄Identity的配置流程。
上一篇提到新增自 Identity 的 DataModel 的模型屬性與 Identity 如何更新資料庫結構。
這次主要介紹 :
- 更加了解認證(聲明),可達到更客製化的權限控管
- 對於當前使用者增加認證(聲明)資訊
認證(聲明 Claims) :
A. 理解聲明(認證) 建立 ClaimsController :
using System.Security.Claims; // 建立聲明的組件
using System.Web;
using System.Web.Mvc;
namespace MvcIdentityTest2.Controllers
{
public class ClaimsController : Controller
{
[Authorize]
public ActionResult Index()
{
//宣告聲明 並使用 當前使用者的 Identity 物件轉型
ClaimsIdentity ident = HttpContext.User.Identity as ClaimsIdentity;
if (ident == null)
return View("_Error", new string[] { "取得聲明失敗" });
else
return View(ident.Claims);
}
}
}
B. 建立 ClaimsController 中的 Index 所對應的頁面。於 \Views\Claims\ 建立 Index.cshtml :
@using System.Security.Claims @using MvcIdentityTest2.Infrastructure @model IEnumerable<Claim>
@{
ViewBag.Title = "Claims";
}
<h2>Claims</h2>
<div class="panel panel-primary">
<div class="panel-heading">
Claims
</div>
<table class="table table-striped">
<tr>
<th>Subject</th>
<th>Issuer</th>
<th>Type</th>
<th>Value</th>
</tr>
@foreach (var claim in Model.OrderBy(x => x.Type))
{
<tr>
<td>
@claim.Subject.Name
</td>
<td>
@claim.Issuer
</td>
<td>
@Html.ClaimType(claim.Type)
</td>
<td>
@claim.Value
</td>
</tr>
}
</table>
</div>
C. 新增HtmlHelp擴充方法於 IdentityHelpers 類別 :
只顯示修改程式碼與引入組件 =>
using System.Reflection;
using System.Security.Claims;
using System.Linq;
public static class IdentityHelpers
{
....
/// <summary>
/// 自定義的擴充方法 主要分析 <see cref="Claim"/> 聲明後拋出對應的字串
/// </summary>
public static MvcHtmlString ClaimType(this HtmlHelper html, string inClaimType)
{
//FieldInfo 裝載 物件欄位集合
FieldInfo[] fields = typeof(ClaimTypes).GetFields();
foreach(FieldInfo field in fields)
{
if(field.GetValue(null).ToString() == inClaimType)
{
return new MvcHtmlString(field.Name);
}
}
return new MvcHtmlString(string.Format("{0}",
inClaimType.Split(',', '.').Last()));
}
}
IIdentity idetn = HttpContext.User.Identity; 此時能使用的屬性 :
var str1 = ident.AuthenticationType; // 使用者此次的認證機制
var bool1 = ident.IsAuthenticated; // 使用者是否經過認證
var name = ident.Name; //使用者名稱
ClaimsIdentity idetn2 = HttpContext.User.Identity as ClaimsIdentity; 此時能使用的屬性 :
var str1 = ident.AuthenticationType; // 使用者此次的認證機制
var bool1 = ident.IsAuthenticated; // 使用者是否經過認證
var name = ident.Name; //使用者名稱
// 此時還能在取得更細節的聲明資訊,針對單一聲明(Claim)
Claim claim = ident.Claims.GetEnumerator().Current; 此時能取得當前第一個元素的聲明 :
var issuer = claim.Issuer; // 取得聲明授權者
var subject = claim.Subject; // 取得聲明主體名稱
var type = claim.Type; // 取得該聲明的類型(並非值的類型,是MVC作判別該聲明是什麼種類的聲明)
var value = claim.Value; // 取得聲明的值
D. 創建自定義聲明(認證) \Infrastructure\ 在該資料夾下建立 LocationClaimsProvider 類別 :
using System.Collections.Generic;
using System.Security.Claims;
namespace MvcIdentityTest2.Infrastructure
{
public class LocationClaimsProvider
{
/// <summary>
/// 額外增加 聲明資訊
/// </summary>
public static IEnumerable<Claim> GetClaims(ClaimsIdentity userIdent)
{
List<Claim> claims = new List<Claim>();
// 若該聲明使用者名稱為 alice
if (userIdent.Name.ToLower() == "alice")
{
claims.Add(CreateClaim(ClaimTypes.PostalCode, "DC 20500"));
claims.Add(CreateClaim(ClaimTypes.StateOrProvince, "DC"));
}
else
{
claims.Add(CreateClaim(ClaimTypes.PostalCode, "NY 10036"));
claims.Add(CreateClaim(ClaimTypes.StateOrProvince, "NY"));
}
return claims;
}
// 建立聲明
private static Claim CreateClaim(string type, string value)
{
//參數定義 : 聲明類型、聲明值、聲明值型別、授權者
return new Claim(type, value, ClaimValueTypes.String, "RemoteClaims");
}
}
}
E. 認證過程期間,聲明(Claims)是於 AccountController 賦予的,再來修改 Login 的方法 :
只顯示修改程式碼 =>
[Authorize]
public class AccountController : MyBaseController
{
....
[HttpPost][AllowAnonymous][ValidateAntiForgeryToken] // 預防跨網頁攻擊
public async Task<ActionResult> Login(LoginModel details, string returnUrl)
{
// 前端的頁面是否已通過驗證??
if (ModelState.IsValid)
{
// 使用 Identity 中的方法,使用 使用者姓名 與 密碼 找尋,若有的話則傳回一個對應使用者的 DataModel
AppUser user = await base.BaseUserManager.FindAsync(details.Name, details.Password);
if (user != null)
{
// 依據對應的 DataModle 建立起 認證機制的物件 ,
ClaimsIdentity ident = await base.BaseUserManager.CreateIdentityAsync(user,
DefaultAuthenticationTypes.ApplicationCookie);
// 使用 LocationClaimsProvider 添加自定義聲明 <---修改部分
ident.AddClaims(Infrastructure.LocationClaimsProvider.GetClaims(ident));
// 先將當前使用者的 Cookie 認證清除
base.BaseAuthManager.SignOut();
// 將當前使用者使用 Cookie 賦予相關的認證資訊。 IsPersistent = false 是讓 Cookie 不為永久性的
// 此時 [Authorize] 最基本的驗證已可使用
base.BaseAuthManager.SignIn(new AuthenticationProperties() { IsPersistent = false }, ident);
// 跳轉至登入前頁面
return Redirect(returnUrl);
}
ModelState.AddModelError("", "使用者名稱或密碼錯誤");
}
ViewBag.returnUrl = returnUrl; // 儲存使用者 登入前頁面的 URL
return View(details);
}
....
}
此時運行網站時使用分別 Admin Alice 使用者登入,觀察兩位使用者的聲明變化!! 可以看到 兩位使用者 分別加入了我們剛剛自定義的聲明
F. 根據自定義的聲明內容,額外再添加給Controller授權屬性作判別的聲明 !! 代表可以使用聲明達到更客製化的效果。
-
在 \Infrastructure\ 資料夾下建立 ClaimsRoles 類別 :
using System.Collections.Generic; using System.Security.Claims; namespace MvcIdentityTest2.Infrastructure { public class ClaimsRoles { /// <summary> /// 針對特殊條件下的聲明 增加額外的聲明資訊 /// </summary> public static IEnumerable<Claim> CreateRolesFromCliams(ClaimsIdentity userIdent) { List<Claim> claims = new List<Claim>(); // 對傳遞進來的認證物件 做條件的篩選 : // 當授權者為 RemoteClaims && 聲明值為 DC && 權限類別的聲明值為 Employees if (userIdent.HasClaim(x => x.Type == ClaimTypes.StateOrProvince && x.Issuer == "RemoteClaims" && x.Value == "DC") && userIdent.HasClaim(x => x.Type == ClaimTypes.Role && x.Value == "Employees")) { // 建立聲明 : 聲明型態、聲明值 claims.Add(new Claim(ClaimTypes.Role, "DCStaff")); } return claims; } } }
-
再來修改 AccountController 中的 Login 方法 :
只顯示修改程式碼 =>[Authorize] public class AccountController : MyBaseController { .... [HttpPost][AllowAnonymous][ValidateAntiForgeryToken] // 預防跨網頁攻擊 public async Task<ActionResult> Login(LoginModel details, string returnUrl) { // 前端的頁面是否已通過驗證?? if (ModelState.IsValid) { // 使用 Identity 中的方法,使用 使用者姓名 與 密碼 找尋,若有的話則傳回一個對應使用者的 DataModel AppUser user = await base.BaseUserManager.FindAsync(details.Name, details.Password); if (user != null) { // 依據對應的 DataModle 建立起 認證機制的物件 , ClaimsIdentity ident = await base.BaseUserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie); // 使用 LocationClaimsProvider 添加自定義聲明 ident.AddClaims(Infrastructure.LocationClaimsProvider.GetClaims(ident)); // 使用 CreateRolesFromCliams 針對特殊的聲明條件添加自定義聲明 <---修改部分 ident.AddClaims(Infrastructure.ClaimsRoles.CreateRolesFromCliams(ident)); // 先將當前使用者的 Cookie 認證清除 base.BaseAuthManager.SignOut(); // 將當前使用者使用 Cookie 賦予相關的認證資訊。 IsPersistent = false 是讓 Cookie 不為永久性的 // 此時 [Authorize] 最基本的驗證已可使用 base.BaseAuthManager.SignIn(new AuthenticationProperties() { IsPersistent = false }, ident); // 跳轉至登入前頁面 return Redirect(returnUrl); } ModelState.AddModelError("", "使用者名稱或密碼錯誤"); } ViewBag.returnUrl = returnUrl; // 儲存使用者 登入前頁面的 URL return View(details); } .... }
-
修改 ClaimsController :
只顯示修改程式碼 =>public class ClaimsController : Controller { .... [Authorize(Roles = "DCStaff")] public string OtherAction() { return "This is the protected action"; } }
做到了這裡可以運行網站,並使用 Alice 與 Admin 登入後訪問 ClaimsController 中的 OtherAction 方法。可以看到 Alice 才有辦法訪問該方法。
G. 自定義 Controller 的 [Authorize]驗證屬性(Attribute)。
-
於 \Infrastructure\ 資料夾下建立 ClaimsAccessAttribute 類別 :
using System.Security.Claims; using System.Web; using System.Web.Mvc; namespace MvcIdentityTest2.Infrastructure { /// <summary> /// 增加 Controller 的屬性。類別名稱後面加 Attribute 且繼承自相關屬性的類別,即可在 Controller 使用 /// </summary> public class ClaimsAccessAttribute : AuthorizeAttribute { /// <summary> /// 簽章者名稱 /// </summary> public string Issuer { get; set; } /// <summary> /// 聲明型態 /// </summary> public string ClaimsType { get; set; } /// <summary> /// 聲明內容(值) /// </summary> public string Value { get; set; } protected override bool AuthorizeCore(HttpContextBase httpContext) { //return base.AuthorizeCore(httpContext); return httpContext.User.Identity.IsAuthenticated && //當前使用者是否以使用聲明驗證 httpContext.User.Identity is ClaimsIdentity && //確認當前用戶的驗證型態 (httpContext.User.Identity as ClaimsIdentity).HasClaim(x => x.Issuer == this.Issuer && x.Type == this.ClaimsType && x.Value == this.Value); } } }
-
修改 ClaimsController,套用自定義屬性(Attribute) :
只顯示修改程式碼與引入組件 =>using System.Security.Claims; // 建立聲明的組件 using System.Web.Mvc; using MvcIdentityTest2.Infrastructure; // 使用自定義屬性時需引入命名空間 public class ClaimsController : Controller { .... // 使用自定義屬性 [ClaimsAccess(Issuer = "RemoteClaims", ClaimsType = ClaimTypes.PostalCode, Value = "NY 10036")] public string OtherAction2() { return "This is the protected action"; } }
做到這裡運行網站使用 Alice && Admin 登入網站,並試著訪問 ClaimsController 中的方法觀其變化。
第三方認證 大致講解原理 :
-
Identity 使用 第三方的Api服務 傳回的 Cookie認證,創建對應的使用者並且為該使用者加入聲明
-
網站若要是第三方認證就不應該授予使用網站自身創建帳號,這在網站端管理起來會較為複雜尤其是在使用者帳密規範這一塊,因為就算使用第三方認證還是會跑基本原本設定的規範程序。 (邏輯上可以做到,但會使規範類別較為複雜)
- 關於第三方認證可參考 : 推薦網站
※到了這裡應該要了解 :
-
對於聲名(認證)更加了解,Identity 加入 ClaimsIdentity 到當前使用者的 Cookie認證資訊中 :
a. 聲明的關聯性 => Claim -> Claims -> ClaimsIdentity -> IIdentity
b. 了解 IIdentity 聲明的相關屬性。EX: Name、IsAuthenticated、Iusser、Type、Value...等屬性
c. 自定義 Claims 並使用 ClaimsIdentity 添加後再使用 AuthManage 註冊當前使用者 -
Controller 的認證機制是透過當前使用者的 IIdentity 這個物件資訊來做判斷
-
自定義 Controller 的 Attribute屬性
總結 :
使用 Identity 提供的機制,已能方便管理會員與權限機制,並且透過聲明讓Controller判別權限更加輕便。
若對 Identity && Owin 類別有所領悟那在會員機制的使用上應能達到很多需求與目的了,且有基本的安全機制。
多多指教!! 歡迎交流!!
你不知道自己不知道,那你會以為你知道