ASP.NET Core Authorization

本文目录

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

ASP.NET Core Authorization的更多相关文章

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

    本文转自:http://www.cnblogs.com/rohelm/p/Authorization.html 本文目录 Asp.net Core 对于授权的改动很友好,非常的灵活,本文以MVC为主, ...

  2. 教你实践ASP.NET Core Authorization

    本文目录 Asp.net Core 对于授权的改动很友好,非常的灵活,本文以MVC为主,当然如果说webapi或者其他的分布式解决方案授权,也容易就可以实现单点登录都非常的简单,可以使用现成的Iden ...

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

    There are several reasons why it makes sense to build super-lightweight HTTP services (or, despite a ...

  4. 从头编写asp.net core 2.0 web api 基础框架 (5) + 使用Identity Server 4建立Authorization Server (7) 可运行前后台源码

    前台使用angular 5, 后台是asp.net core 2.0 web api + identity server 4. 从头编写asp.net core 2.0 web api 基础框架: 第 ...

  5. ASP.Net Core下Authorization的几种方式 - 简书

    原文:ASP.Net Core下Authorization的几种方式 - 简书 ASP.Net Core下Authorization的几种方式 Authorization其目标就是验证Http请求能否 ...

  6. Add JWT Bearer Authorization to Swagger and ASP.NET Core

    Add JWT Bearer Authorization to Swagger and ASP.NET Core     If you have an ASP.NET Core web applica ...

  7. ASP.NET Core Authentication and Authorization

    最近把一个Asp .net core 2.0的项目迁移到Asp .net core 3.1,项目启动的时候直接报错: InvalidOperationException: Endpoint CoreA ...

  8. ASP.NET Core框架探索之Authorization

    今天我们一起来探索一下ASP.NET Core框架中的Authorization.我们知道请求进入管道处理流程先会使用Authentication进行用户认证,然后使用Authorization进行用 ...

  9. 理解ASP.NET Core - 授权(Authorization)

    注:本文隶属于<理解ASP.NET Core>系列文章,请查看置顶博客或点击此处查看全文目录 之前,我们已经了解了ASP.NET Core中的身份认证,现在,我们来聊一下授权. 老规矩,示 ...

随机推荐

  1. 让正则表达式变简单(PythonVerbalExpressions)

    一.安装pip install VerbalExpressions二.导入from verbalexpressions import VerEx三.示例 使用案例一: 测试URL是否有效 from v ...

  2. HTTP status codes

    响应码由三位十进制数字组成,它们出现在由HTTP服务器发送的响应的第一行.响应码分五种类型,由它们的第一位数字表示:1.1xx:信息,请求收到,继续处理2.2xx:成功,行为被成功地接受.理解和采纳3 ...

  3. {% load staticfiles %}

    原先写法 {# <link rel="stylesheet" href="/static/css/reset.css"/>#} 不使用路径写法,方便 ...

  4. 转:sublime text快捷键 (很实用的东东)

    一个好的编辑器,能大大提高编程的效率.如果能熟知软件的快捷键,那更能让你得心印手.这些内容都是我网上和自己实际使用过程中所收集而来的,在网络上应该也算比较全面的了吧.欢迎大家补充,我也会在以后慢慢添加 ...

  5. Error creating bean with name 'enableRedisKeyspaceNotificationsInitializer'

    @Configuration public class HttpSessionConfig { @Bean public static ConfigureRedisAction configureRe ...

  6. poj 1458 Common Subsequence(区间dp)

    题目链接:http://poj.org/problem?id=1458 思路分析:经典的最长公共子序列问题(longest-common-subsequence proble),使用动态规划解题. 1 ...

  7. paip.tree 生成目录树到txt后的折叠查看

    paip.tree 生成目录树到txt后的折叠查看 作者Attilax ,  EMAIL:1466519819@qq.com  来源:attilax的专栏 地址:http://blog.csdn.ne ...

  8. PHPer的等级划分

    PHPer的等级划分 前一段时间刚刚完成PHP的培训,然后想知道自己目前的水平(或者说等级),并且应该在哪些方面进行提高,所以在网上查了一下相关介绍.其中有一篇介绍讲的挺清楚的,至少目前的我还是很认同 ...

  9. xcode生成的IOS安装文件的位置

    通过xcode生成可以在IOS系统下运行的文件的具体设置: 1.首先,需要有相应的程序,并且在mac下的xcode编译后,能够在模拟器中完美运行. 2.单击xcode,打开Xcode > Pre ...

  10. Pison geeker

    Pison on scriptogr.am Pison Abraham Lincoln: "Nearly all men can stand adversity, but if you wa ...