[轉]教你實踐ASP.NET Core Authorization

本文轉自:http://www.cnblogs.com/rohelm/p/Authorization.htmlhtml

 

本文目錄

   Asp.net Core 對於受權的改動很友好,很是的靈活,本文以MVC爲主,固然若是說webapi或者其餘的分佈式解決方案受權,也容易就能夠實現單點登陸都很是的簡單,能夠使用現成的IdentityServer框架或者自定義實現動很是方便和乾淨,若是你在運行示例代碼的時候未達到預期效果,請把文章拉到結尾尋找答案。git

本文示例代碼下載,github我這訪問不了,暫且直接上傳博客園存儲了。github

準備

  1. 建立一個名爲AuthorizationForoNetCore的(web)解決方案,選擇Empty模板
  2. 添加相關nuget包引用Microsoft.AspNetCore.Mvc(選擇最新版本)
  3. 編輯Startup.cs文件,添加mvcservice並進行默認路由配置
    複製代碼
     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     }
    複製代碼

     

  4. 添加Controllers文件夾,添加HomeContrller web

    複製代碼
     public class HomeController : Controller
        {
            public IActionResult Index()
            {
                return View();
            }
        }
    複製代碼

     

  5. 建立Views/Home文件夾,並添加Index(Action)對應的Index.cshtml文件api

    1
    2
    3
    4
    5
    6
    <!--Index.cshtml-->
    假如生活欺騙了你
    假如生活欺騙了你,
    不要悲傷,不要心急!
    憂鬱的日子裏需要鎮靜:
    相信吧,快樂的日子將會來臨!   

 使用Authorization

  1. 添加相關nuget包(均使用最新版本)
    1. Microsoft.AspNetCore.Authorization
    2. Microsoft.AspNetCore.Authentication.Cookies
  2. 在ConfigureServices()方法中添加對應服務:  services.AddAuthorization()
  3. Index(Action)方法上添加 [Authorize] 特性,毫無疑問,添加後執行dotnet run 指令後後會返回401的受權碼,那麼接着操做
  4. 編輯Startup.csConfigureapp.UseMvc()方法以前,咱們添加一個cookie 中間件,用於持久化請求管道中的身份配置信息
    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
    });  
  5. tip:相關配置參數細節請參閱:https://docs.asp.net/en/latest/security/authentication/cookie.html安全

  6. 添加Controllers/Account文件夾,添加 AccountController.cs 控制器文件,實現上述指定的方法,可能這裏你會疑惑,爲何文檔裏不是一個 /Account/Login 這類的,文檔說了別較真,這就是個例子而已,繼續你就明白了。
  7. 添加並實現上述中間件重定向的action 方法以下,你能夠看到其實Unauthorized方法模擬實現了登錄的過程。tip:假如你添加Unauthorized視圖,而且沒有該不實現模擬登錄,那麼運行你會直接看到 Unauthorized.cshtml 的內容,這裏咱們不須要添加該視圖,僅做說明。
  8. 複製代碼
    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();
         }
    }
    複製代碼
  9. 編輯 Home/Index.schtmlcookie

    複製代碼
    @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>
    複製代碼
  10. 運行代碼你會看到以下結果(程序獲取咱們提供的由issuer發佈claims並展現在視圖中,後續會檢查Claims看他們是否匹配)

使用全局受權策略

  1. 去除Home/Index (Action)上的  [Authorize]  特性
  2. 添加 Views/Account/Forbidden.cshtml 頁面,內容爲 <h1>拒絕訪問</h1> 
  3. 修改 ConfigureServices 方法中的 services.AddMvc() 使用它的 AddMvc(this IServiceCollection services, Action<MvcOptions> setupAction) 重載
  4. 運行查看結果,你會發現這幾乎成了一個無限的重定向從而形成錯誤,由於每一個頁面都須要受權。
  5. 爲 AccountController 添加 [AllowAnonymous] 特性,啓動匿名訪問,再次運行項目,查看結果
  6. 結果就是重定向到了 Forbidden.cshtml 頁面

使用角色受權

  1. 在 HomeController 上添加 [Authorize(Roles = "Administrator")] 特性
  2. 在模擬登錄處( Unauthorized方法中 )添加角色說明的身份信息條目:
  3.   claims.Add(new Claim(ClaimTypes.Role, "Administrator", ClaimValueTypes.String, "https://www.cnblogs.com/rohelm"));
  4. 運行項目查看結果

能夠使用中你會發現Asp.net Core安全驗證方面較以往的版本最大的改變就是所有采用中間件的方式進行驗證受權,並很好的使用了Policy (策略)這個概念,下那麼繼續~。mvc

基於聲明的受權

  1. 返回Startup.cs,修改 services.AddAuthorization() 方法以下:
    services.AddAuthorization(options =>
    {
        options.AddPolicy("EmployeeOnly", policy => policy.RequireClaim("EmployeeNumber"));
    });
  2. 修改HomeController上的特性,添加 [Authorize(Policy = "EmployeeOnly")] 
  3. 運行項目查看結果,發現被拒絕了
  4. 在模擬登錄處 Unauthorize方法添加:
    claims.Add(new Claim("EmployeeNumber", "123456", ClaimValueTypes.String, "http://www.cnblogs.com/rohelm"));
  5. goto 3.
  6. 多重策略的應用,與以前的版本幾乎同樣,例如本次修改的結果能夠爲:
    複製代碼
     [Authorize(Roles = "Administrator")]
        public class HomeController:Controller 
        {
            [Authorize(Policy = "EmployeeOnly")]
            public IActionResult Index()
            {
                return View();
            }
        } 
    複製代碼
  7. 詳情請參閱:https://docs.asp.net/en/latest/security/authorization/claims.html的說明

 自定義受權策略 

自定義受權策略的實現,包括實現一個 IAuthorizationRequirement 的Requirement,和實現 AuthorizationHandler<TRequirement> 的處理器,這裏使用文檔app

https://docs.asp.net/en/latest/security/authorization/policies.html中的Code。框架

  1. 添加 MinimumAgeHandler 處理器實現
    複製代碼
    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);
                }
            }
        }
    複製代碼
  2. 在 AddAuthorization  中添加一個名爲Over21的策略
    options.AddPolicy("Over21", policy => policy.Requirements.Add(new MinimumAgeRequirement(21)));
  3. 在HomeController上應用該策略  [Authorize(Policy = "Over21")] 
  4. 在 Unauthorized 函數中添加對應的聲明信息條目 claims.Add(new Claim(ClaimTypes.DateOfBirth, "1900-01-01", ClaimValueTypes.Date));
  5. 修改時間(例如小於21歲的生日,2000-01-01)並運行調試,查看結果

對一個Requirement應用多個處理器

tip:上面的演示,咱們使用了一個同時實現AuthorizationHandler<MinimumAgeRequirement>, IAuthorizationRequirement的MinimumAgeRequirement來作演示,可是若是一個Requirement徐要實現多個處理器就須要分開寫了,緣由很簡單,這裏沒法實現類的多重繼承。

下面咱們實現一個使用Token登錄的需求

  1.  添加一個LoginRequirement的需求
        public class LoginRequirement: IAuthorizationRequirement
        {
        }
  2. 添加一個使用用戶名密碼登錄的處理器
    複製代碼
     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);
            }
        }
    複製代碼
  3. 在一些場景中咱們也會使用發放訪問令牌的方式讓用戶登錄
    複製代碼
    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);
                }
            }
        }
    複製代碼
  4. 在 AddAuthorization  中添加一個名爲CanLogin的策略
     options.AddPolicy("CanLogin", policy => policy.Requirements.Add(new LoginRequirement()));
  5. 註冊自定義策略
      services.AddSingleton<IAuthorizationHandler, HasPasswordHandler>();
      services.AddSingleton<IAuthorizationHandler, HasAccessTokenHandler>();
  6. 在Unauthorized 函數中添加對應的聲明信息條目
      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"));
  7. 在HomeController上應用該策略  [Authorize(Policy = "CanLogin")]  
  8. 運行並查看結果。

基於資源的Requirements

在實際開發者中,除了基於用戶的受權驗證外,經過咱們也會遇到針對一些資源的受權限制,例若有的人能夠編輯文檔,有的人只能查看文檔,由此引出該話題

https://docs.asp.net/en/latest/security/authorization/resourcebased.html

  1.  定義一個Document類
    public class Document
        {
            public int Id { get; set; }
            public string Author { get; set; }
        }
  2. 定義Document倉儲接口
    public interface IDocumentRepository
        {
            IEnumerable<Document> Get();
            Document Get(int id);
        }
  3. 模擬實現上述接口
    複製代碼
    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);
            }
        }
    複製代碼
  4. 註冊接口實現類
    services.AddSingleton<IDocumentRepository, FakeDocumentRepository>();
  5. 建立一個 DocumentController 並修改成以下內容
    複製代碼
    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);
            }
        }
    複製代碼
  6. 添加對應 Index.cshtml  視圖文件
    複製代碼
    @model IEnumerable<AuthorizationForoNetCore.Modles.Document>
    
    <h1>文檔列表</h1>
    @foreach (var document in Model)
    {
        <p>
            @Html.ActionLink("文檔 #" + document.Id, "編輯", new { id = document.Id })
        </p>
    }
    複製代碼
  7. 添加對應的 Edit.cshtml 視圖文件
    @model AuthorizationForoNetCore.Modles.Document
    
    <h1>文檔 #@Model.Id</h1>
    <h2>做者: @Model.Author</h2>
  8. 定義EditRequirement
    public class EditRequirement : IAuthorizationRequirement
     {
     }
  9. 添加對應的編輯文檔處理器
    複製代碼
    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);
                }
            }
        }
    複製代碼
  10. 在 ConfigureServices() 方法中註冊處理器實現
    1 services.AddSingleton<IAuthorizationHandler, DocumentEditHandler>();
  11. 因爲對於文檔的受權服務僅僅反正在操做方法的內部,所以咱們須要直接注入 IAuthorizationService 對象並在須要的Action內部直接處理
    複製代碼
    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();
                }
            }
        }
    複製代碼
  12. 運行查看結果

在視圖中進行受權

問題來了額,上面示例的視圖中怎麼作限制了,那就繼續了

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

 
分類:  Asp.net Core
相關文章
相關標籤/搜索