本文轉自:http://www.cnblogs.com/rohelm/p/Authorization.htmlhtml
Asp.net Core 對於受權的改動很友好,很是的靈活,本文以MVC爲主,固然若是說webapi或者其餘的分佈式解決方案受權,也容易就能夠實現單點登陸都很是的簡單,能夠使用現成的IdentityServer框架或者自定義實現動很是方便和乾淨,若是你在運行示例代碼的時候未達到預期效果,請把文章拉到結尾尋找答案。git
本文示例代碼下載,github我這訪問不了,暫且直接上傳博客園存儲了。github
1 public class Startup 2 { 3 public void ConfigureServices(IServiceCollection services) 4 { 5 services.AddMvc(); 6 } 7 8 public void Configure(IApplicationBuilder app) 9 { 10 app.UseMvc(routes => 11 { 12 routes.MapRoute( 13 name: "default", 14 template: "{controller=Home}/{action=Index}/{id?}"); 15 }); 16 } 17 }
添加Controllers文件夾,添加HomeContrller web
public class HomeController : Controller { public IActionResult Index() { return View(); } }
建立Views/Home文件夾,並添加Index(Action)對應的Index.cshtml文件
api
1
2
3
4
5
6
|
<!--Index.cshtml-->
假如生活欺騙了你
假如生活欺騙了你,
不要悲傷,不要心急!
憂鬱的日子裏需要鎮靜:
相信吧,快樂的日子將會來臨!
|
1
2
3
4
5
6
7
8
|
app.UseCookieAuthentication(
new
CookieAuthenticationOptions
{
AuthenticationScheme =
"MyCookieMiddlewareInstance"
,
LoginPath =
new
PathString(
"/Account/Unauthorized/"
),
AccessDeniedPath =
new
PathString(
"/Account/Forbidden/"
),
AutomaticAuthenticate =
true
,
AutomaticChallenge =
true
});
|
tip:相關配置參數細節請參閱:https://docs.asp.net/en/latest/security/authentication/cookie.html安全
public class AccountController : Controller { public async Task<IActionResult> Unauthorized(string returnUrl = null) { List<Claim> claims = new List<Claim>(); claims.Add(new Claim(ClaimTypes.Name, "halower", ClaimValueTypes.String, "https://www.cnblogs.com/rohelm")); var userIdentity = new ClaimsIdentity("管理員"); userIdentity.AddClaims(claims); var userPrincipal = new ClaimsPrincipal(userIdentity); await HttpContext.Authentication.SignInAsync("MyCookieMiddlewareInstance", userPrincipal, new AuthenticationProperties { ExpiresUtc = DateTime.UtcNow.AddMinutes(20), IsPersistent = false, AllowRefresh = false }); if (Url.IsLocalUrl(returnUrl)) { return Redirect(returnUrl); } else { return RedirectToAction("Index", "Home"); } } public IActionResult Forbidden() { return View(); } }
編輯 Home/Index.schtml
cookie
@using System.Security.Claims; @if (User.Identities.Any(u => u.IsAuthenticated)) { <h1> 歡迎登錄 @User.Identities.First(u => u.IsAuthenticated).FindFirst(ClaimTypes.Name).Value </h1> <h2>所使用的身份驗證的類型:@User.Identity.AuthenticationType</h2> } <article> 假如生活欺騙了你<br /> 假如生活欺騙了你<br /> 不要悲傷,不要心急<br /> 憂鬱的日子裏需要鎮靜<br /> 相信吧,快樂的日子將會來臨 </article>
能夠使用中你會發現Asp.net Core安全驗證方面較以往的版本最大的改變就是所有采用中間件的方式進行驗證受權,並很好的使用了Policy (策略)這個概念,下那麼繼續~。mvc
services.AddAuthorization(options => { options.AddPolicy("EmployeeOnly", policy => policy.RequireClaim("EmployeeNumber")); });
claims.Add(new Claim("EmployeeNumber", "123456", ClaimValueTypes.String, "http://www.cnblogs.com/rohelm"));
[Authorize(Roles = "Administrator")] public class HomeController:Controller { [Authorize(Policy = "EmployeeOnly")] public IActionResult Index() { return View(); } }
自定義受權策略的實現,包括實現一個 IAuthorizationRequirement 的Requirement,和實現 AuthorizationHandler<TRequirement> 的處理器,這裏使用文檔app
https://docs.asp.net/en/latest/security/authorization/policies.html中的Code。框架
public class MinimumAgeRequirement: AuthorizationHandler<MinimumAgeRequirement>, IAuthorizationRequirement { int _minimumAge; public MinimumAgeRequirement(int minimumAge) { _minimumAge = minimumAge; } protected override void Handle(AuthorizationContext context, MinimumAgeRequirement requirement) { if (!context.User.HasClaim(c => c.Type == ClaimTypes.DateOfBirth)) { return; } var dateOfBirth = Convert.ToDateTime( context.User.FindFirst(c => c.Type == ClaimTypes.DateOfBirth).Value); int calculatedAge = DateTime.Today.Year - dateOfBirth.Year; if (dateOfBirth > DateTime.Today.AddYears(-calculatedAge)) { calculatedAge--; } if (calculatedAge >= _minimumAge) { context.Succeed(requirement); } } }
options.AddPolicy("Over21", policy => policy.Requirements.Add(new MinimumAgeRequirement(21)));
修改時間(例如小於21歲的生日,2000-01-01)並運行調試,查看結果
tip:上面的演示,咱們使用了一個同時實現AuthorizationHandler<MinimumAgeRequirement>, IAuthorizationRequirement的MinimumAgeRequirement來作演示,可是若是一個Requirement徐要實現多個處理器就須要分開寫了,緣由很簡單,這裏沒法實現類的多重繼承。
下面咱們實現一個使用Token登錄的需求
public class LoginRequirement: IAuthorizationRequirement { }
public class HasPasswordHandler : AuthorizationHandler<LoginRequirement> { protected override void Handle(AuthorizationContext context, LoginRequirement requirement) { if (!context.User.HasClaim(c => c.Type == "UsernameAndPassword" && c.Issuer == "http://www.cnblogs.com/rohelm")) return; context.Succeed(requirement); } }
public class HasAccessTokenHandler : AuthorizationHandler<LoginRequirement> { protected override void Handle(AuthorizationContext context, LoginRequirement requirement) { if (!context.User.HasClaim(c => c.Type == "AccessToken" && c.Issuer == "http://www.cnblogs.com/rohelm")) return; var toeknExpiryIn = Convert.ToDateTime(context.User.FindFirst(c => c.Type == "AccessToken" && c.Issuer == "http://www.cnblogs.com/rohelm").Value); if (toeknExpiryIn > DateTime.Now) { context.Succeed(requirement); } } }
options.AddPolicy("CanLogin", policy => policy.Requirements.Add(new LoginRequirement()));
services.AddSingleton<IAuthorizationHandler, HasPasswordHandler>(); services.AddSingleton<IAuthorizationHandler, HasAccessTokenHandler>();
claims.Add(new Claim("UsernameAndPassword", "123456", ClaimValueTypes.String, "http://www.cnblogs.com/rohelm"));
// 測試切換登錄聲明方式
// claims.Add(new Claim("AccessToken", DateTime.Now.AddMinutes(1).ToString(), ClaimValueTypes.String, "http://www.cnblogs.com/rohelm"));
在實際開發者中,除了基於用戶的受權驗證外,經過咱們也會遇到針對一些資源的受權限制,例若有的人能夠編輯文檔,有的人只能查看文檔,由此引出該話題
https://docs.asp.net/en/latest/security/authorization/resourcebased.html
public class Document { public int Id { get; set; } public string Author { get; set; } }
public interface IDocumentRepository { IEnumerable<Document> Get(); Document Get(int id); }
public class FakeDocumentRepository : IDocumentRepository { static List<Document> _documents = new List<Document> { new Document { Id = 1, Author = "halower" }, new Document { Id = 2, Author = "others" } }; public IEnumerable<Document> Get() { return _documents; } public Document Get(int id) { return _documents.FirstOrDefault(d => d.Id == id); } }
services.AddSingleton<IDocumentRepository, FakeDocumentRepository>();
public class DocumentController : Controller { IDocumentRepository _documentRepository; public DocumentController(IDocumentRepository documentRepository) { _documentRepository = documentRepository; } public IActionResult Index() { return View(_documentRepository.Get()); } public IActionResult Edit(int id) { var document = _documentRepository.Get(id); if (document == null) return new NotFoundResult(); return View(document); } }
@model IEnumerable<AuthorizationForoNetCore.Modles.Document> <h1>文檔列表</h1> @foreach (var document in Model) { <p> @Html.ActionLink("文檔 #" + document.Id, "編輯", new { id = document.Id }) </p> }
@model AuthorizationForoNetCore.Modles.Document <h1>文檔 #@Model.Id</h1> <h2>做者: @Model.Author</h2>
public class EditRequirement : IAuthorizationRequirement { }
public class DocumentEditHandler : AuthorizationHandler<EditRequirement, Document> { protected override void Handle(AuthorizationContext context, EditRequirement requirement, Document resource) { if (resource.Author == context.User.FindFirst(ClaimTypes.Name).Value) { context.Succeed(requirement); } } }
1 services.AddSingleton<IAuthorizationHandler, DocumentEditHandler>();
public class DocumentController : Controller { IDocumentRepository _documentRepository; IAuthorizationService _authorizationService; public DocumentController(IDocumentRepository documentRepository, IAuthorizationService authorizationService) { _documentRepository = documentRepository; _authorizationService = authorizationService; } public IActionResult Index() { return View(_documentRepository.Get()); } public async Task<IActionResult> Edit(int id) { var document = _documentRepository.Get(id); if (document == null) return new NotFoundResult(); if (await _authorizationService.AuthorizeAsync(User, document, new EditRequirement())) { return View(document); } else { return new ChallengeResult(); } } }
問題來了額,上面示例的視圖中怎麼作限制了,那就繼續了
1.使用 @inject 命令注入 AuthorizationService
2.應用該上述一樣策略,作簡單修改
@using Microsoft.AspNetCore.Authorization @model IEnumerable<AuthorizationForoNetCore.Modles.Document> @inject IAuthorizationService AuthorizationService @using AuthorizationForoNetCore.Policy <h1>文檔列表</h1> @{ var requirement = new EditRequirement(); foreach (var document in Model) { if (await AuthorizationService.AuthorizeAsync(User, document, requirement)) { <p> @Html.ActionLink("文檔 #" + document.Id, "編輯", new { id = document.Id }) </p> } } }
請在運行時清理Cookie,或者在試驗時直接暫時禁用
以前寫的一個插件,誰有時間幫升級支持下asp.net Core:https://github.com/halower/JqGridForMvc