[转]教你实践ASP.NET Core Authorization

本文转自:http://www.cnblogs.com/rohelm/p/Authorization.html

本文目录

Asp.net Core 对于授权的改动很友好,非常的灵活,本文以MVC为主,当然如果说webapi或者其他的分布式解决方案授权,也容易就可以实现单点登录都非常的简单,可以使用现成的IdentityServer框架或者自定义实现动非常方便和干净,如果你在运行示例代码的时候未达到预期效果,请把文章拉到结尾寻找答案。

本文示例代码下载,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 

     public class HomeController : Controller
        {
            public IActionResult Index()
            {
                return View();
            }
        }

  5. 创建Views/Home文件夹,并添加Index(Action)对应的Index.cshtml文件

    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.schtml

    @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 (策略)这个概念,下那么继续~。

基于声明的授权

  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> 的处理器,这里使用文档

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

时间: 2024-10-20 16:53:01

[转]教你实践ASP.NET Core Authorization的相关文章

教你实践ASP.NET Core Authorization

参考页面: http://www.yuanjiaocheng.net/ASPNET-CORE/core-razor-layout.html http://www.yuanjiaocheng.net/ASPNET-CORE/core-view-start.html http://www.yuanjiaocheng.net/ASPNET-CORE/core-import-view.html http://www.yuanjiaocheng.net/ASPNET-CORE/core-razor-tag

ASP.NET Core Authorization

ASP.NET Core Authorization 本文目录 Asp.net Core 对于授权的改动很友好,非常的灵活,本文以MVC为主,当然如果说webapi或者其他的分布式解决方案授权,也容易就可以实现单点登录都非常的简单,可以使用现成的IdentityServer框架或者自定义实现动非常方便和干净,如果你在运行示例代码的时候未达到预期效果,请把文章拉到结尾寻找答案. 本文示例代码下载,github我这访问不了,暂且直接上传博客园存储了. 准备 使用Authorization 使用全局授

Building microservices with ASP.NET Core (without MVC)(转)

There are several reasons why it makes sense to build super-lightweight HTTP services (or, despite all the baggage the word brings, “microservices”). I do not need to go into all the operational or architectural benefits of such approach to system de

ASP.NET Core OceLot 微服务实践

1.OceLot中间件介绍 在传统的BS应用中,随着业务需求的快速发展变化,需求不断增长,迫切需要一种更加快速高效的软件交付方式.微服务可以弥补单体应用不足,是一种更加快速高效软件架构风格.单体应用被分解成多个更小的服务,每个服务有自己的独立模块,单独部署,然后共同组成一个应用程序.把范围限定到单个独立业务模块功能.分布式部署在各台服务器上. 而Ocelot开发的目标就是使用.NET运行面向微服务/服务的架构,要达到这个目标需要一个统的系统入口点(我们统称为API网关),同时需要与Identit

Asp.Net Core 中间件应用实践中你不知道的那些事

一.概述 这篇文章主要分享Endpoint 终结点路由的中间件的应用场景及实践案例,不讲述其工作原理,如果需要了解工作原理的同学, 可以点击查看以下两篇解读文章: Asp.Net Core EndPoint 终结点路由工作原理解读 ASP.NET CORE 管道模型及中间件使用解读 1.1 中间件(Middleware)的作用 我们知道,任何的一个web框架都是把http请求封装成一个管道,每一次的请求都是经过管道的一系列操作,最终到达我们写的代码中.那么中间件就是在应用程序管道中的一个组件,用

ASP.NET Core MVC TagHelper实践HighchartsNET快速图表控件-开源

ASP.NET Core MVC TagHelper最佳实践HighchartsNET快速图表控件支持ASP.NET Core. 曾经在WebForms上写过 HighchartsNET快速图表控件-开源 Highcharts的ASP.NET Web自定义控件. 今天我就来改造它,将其使用最新的TagHelper 来实践,学习TagHelper 的使用也提供一个方便的图表控件在ASP.NET Core MVC中使用. 下面正式开始,使用之前的代码直接进行迁移升级. GitHub:https://

使用Docker部署ASP.NET Core应用程序实践

前言 最近把很火的Docker给看了,于是就磨拳擦掌要去实践一下.于是就拿之前一个aps.net core的项目(已被停止)去练手.该项目之前在ubuntu14.04上确保可以正常运行,所以docker化应该不会有太多问题.搜索了下微软提供了asp.net core的官方docker镜像,但我为了学习docker决定从linux的基础镜像开始制作docker镜像,同时为了少绕弯路,决定从确保能运行的ubuntu14.04开始. 准备 环境 OS:Ubuntu 14.04 .NET Core SD

ASP.NET Core 依赖注入最佳实践——提示与技巧

在这篇文章,我将分享一些在ASP.NET Core程序中使用依赖注入的个人经验和建议.这些原则背后的动机如下: 高效地设计服务和它们的依赖. 预防多线程问题. 预防内存泄漏. 预防潜在的BUG. 这篇文章假设你已经基本熟悉依赖注入和ASP.NET Core.如果不是,则先阅读文章: 在ASP.NET Core中使用依赖注入 基础 构造函数注入 构造函数注入常用于在服务构建上定义和获取服务依赖.例如: 1 public class ProductService 2 { 3 private read

ASP.NET Core在CentOS上的最小化部署实践

原文:ASP.NET Core在CentOS上的最小化部署实践 引言 本文从Linux小白的视角, 在CentOS 7.x服务器上搭建一个Nginx-Powered AspNet Core Web准生产应用. 在开始之前,我们还是重温一下部署原理,正如你所常见的.Net Core 部署图: 在Linux上部署.Net Core App最好的方式是在Linux机器上使用Kestrel 服务在端口5000上支撑web应用: 然后设置Nginx作为反向代理服务器,将输入请求转发给Kestrel服务器,