ASP.NET Core Authentication and Authorization

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

InvalidOperationException: Endpoint CoreAuthorization.Controllers.HomeController.Index (CoreAuthorization) contains authorization metadata, but a middleware was not found that supports authorization.
Configure your application startup by adding app.UseAuthorization() inside the call to Configure(..) in the application startup code. The call to app.UseAuthorization() must appear between app.UseRouting() and app.UseEndpoints(...).
Microsoft.AspNetCore.Routing.EndpointMiddleware.ThrowMissingAuthMiddlewareException(Endpoint endpoint)

看意思是缺少了一个authorization的中间件,这个项目在Asp.net core 2.0上是没问题的。
startup是这样注册的:

public class Startup{public Startup(IConfiguration configuration){Configuration = configuration;}public IConfiguration Configuration { get; }// This method gets called by the runtime. Use this method to add services to the container.public void ConfigureServices(IServiceCollection services){services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(options =>{options.LoginPath = "/account/Login";});services.AddControllersWithViews();}// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.public void Configure(IApplicationBuilder app, IWebHostEnvironment env){if (env.IsDevelopment()){app.UseDeveloperExceptionPage();}else{app.UseExceptionHandler("/Home/Error");// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.app.UseHsts();}//app.UseHttpsRedirection();app.UseStaticFiles();app.UseRouting();app.UseAuthentication();app.UseEndpoints(endpoints =>{endpoints.MapControllerRoute(name: "default",pattern: "{controller=Home}/{action=Index}/{id?}");});}}

查了文档后发现3.0的示例代码多了一个UseAuthorization,改成这样就可以了:

 app.UseRouting();app.UseAuthentication();//use授权中间件app.UseAuthorization();app.UseEndpoints(endpoints =>{endpoints.MapControllerRoute(name: "default",pattern: "{controller=Home}/{action=Index}/{id?}");});

看来Asp .net Core 3.1的认证跟授权又不太一样了,只能继续看文档学习了。

UseAuthentication and UseAuthorization

先说一下Authentication跟Authorization的区别。这两个单词长的十分相似,而且还经常一起出现,很多时候容易搞混了。

1.Authentication是认证,明确是你谁,确认是不是合法用户。常用的认证方式有用户名密码认证。2.Authorization是授权,明确你是否有某个权限。当用户需要使用某个功能的时候,系统需要校验用户是否需要这个功能的权限。
所以这两个单词是不同的概念,不同层次的东西。UseAuthorization在asp.net core 2.0中是没有的。在3.0之后微软明确的把授权功能提取到了Authorization中间件里,所以我们需要在UseAuthentication之后再次UseAuthorization。否则,当你使用授权功能比如使用[Authorize]属性的时候系统就会报错。

Authentication(认证)

认证的方案有很多,最常用的就是用户名密码认证,下面演示下基于用户名密码的认证。新建一个MVC项目,添加AccountController:

        [HttpPost]public async Task<IActionResult> Login([FromForm]string userName, [FromForm]string password){//validate username password...var claims = new List<Claim>{new Claim(ClaimTypes.Name, userName),new Claim(ClaimTypes.Role, "老师")};var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme,new ClaimsPrincipal(claimsIdentity));return Redirect("/");}public async Task<IActionResult> Logoff(){await HttpContext.SignOutAsync();return Redirect("Login");}public IActionResult AccessDenied(){return Content("AccessDenied");}

修改login.cshtml

@{ViewData["Title"] = "Login Page";
}<h1>Login Page</h1><form method="post"><p>用户名: <input name="userName" value="administrator" /></p><p>密码: <input name="password" value="123" /></p><p><button>登录</button></p></form>

从前台传入用户名密码后进行用户名密码校验(示例代码省略了密码校验)。如果合法,则把用户的基本信息存到一个claim list里,并且指定cookie-base的认证存储方案。最后调用SignInAsync把认证信息写到cookie中。根据cookie的特性,接来下所有的http请求都会携带cookie,所以系统可以对接来下用户发起的所有请求进行认证校验。Claim有很多翻译,个人觉得叫“声明”比较好。一单认证成功,用户的认证信息里就会携带一串Claim,其实就是用户的一些信息,你可以存任何你觉得跟用户相关的东西,比如用户名,角色等,当然是常用的信息,不常用的信息建议在需要的时候查库。调用HttpContext.SignOutAsync()方法清除用户登认证信息。
Claims信息我们可以方便的获取到:

@{ViewData["Title"] = "Home Page";
}<h2>CoreAuthorization</h2><p>@Context.User.FindFirst(System.Security.Claims.ClaimTypes.Name)?.Value
</p>
<p>角色:@foreach (var claims in Context.User.Claims.Where(c => c.Type == System.Security.Claims.ClaimTypes.Role)){<span> @claims.Value </span>}
</p>
<p><a href="/Student/index">/Student/index</a>
</p>
<p><a href="/Teacher/index">/Teacher/Index</a>
</p>
<p><a href="/Teacher/Edit">/Student/Edit</a>
</p><p><a href="/Account/Logoff">退出</a>
</p>

改一下home/Index页面的html,把这些claim信息展示出来。

以上就是一个基于用户名密码以及cookie的认证方案。

Authorization(授权)

有了认证我们还需要授权。刚才我们实现了用户名密码登录认证,但是系统还是没有任何管控,用户可以随意查库任意页面。现实中的系统往往都是某些页面可以随意查看,有些页面则需要认证授权后才可以访问。

AuthorizeAttribute

当我们希望一个页面只有认证后才可以访问,我们可以在相应的Controller或者Action上打上AuthorizeAttribute这个属性。修改HomeController:

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

重新启动网站,如果没有登录,访问home/index的时候网站会跳转到/account/AccessDenied。如果登录后则可以正常访问。AuthorizeAttribute默认授权校验其实是把认证跟授权合为一体了,只要认证过,就认为有授权,这是也是最最简单的授权模式。

基于角色的授权策略

显然上面默认的授权并不能满足我们开发系统的需要。AuthorizeAttribute还内置了基于Role(角色)的授权策略。
登录的时候给认证信息加上角色的声明:

  [HttpPost]public async Task<IActionResult> Login([FromForm]string userName, [FromForm]string password){//validate username passwordvar claims = new List<Claim>{new Claim(ClaimTypes.Name, userName),new Claim(ClaimTypes.Role, "老师"),};var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme,new ClaimsPrincipal(claimsIdentity));return Redirect("/");}

新建一个TeacherController:

    [Authorize(Roles = "老师")]public class TeacherController : Controller{public IActionResult Index(){return Content("Teacher index");}}

给AuthorizeAttribute的属性设置Roles=老师,表示只有老师角色的用户才可以访问。如果某个功能可以给多个角色访问那么可以给Roles设置多个角色,使用逗号进行分割。

  [Authorize(Roles = "老师,校长")]public class TeacherController : Controller{public IActionResult Index(){return Content("Teacher index");}}

这样认证的用户只要具有老师或者校长其中一个角色就可以访问。

基于策略的授权

上面介绍了内置的基于角色的授权策略。如果现实中需要更复杂的授权方案,我们还可以自定义策略来支持。比如我们下面定义一个策略:编辑功能只能姓王的老师可以访问。
定义一个要求:

 public class LastNamRequirement : IAuthorizationRequirement{public string LastName { get; set; }}

IAuthorizationRequirement其实是一个空接口,仅仅用来标记,继承这个接口就是一个要求。这是空接口,所以要求的定义比较宽松,想怎么定义都可以,一般都是根据具体的需求设置一些属性。比如上面的需求,本质上是根据老师的姓来决定是否授权通过,所以把姓作为一个属性暴露出去,以便可以配置不同的姓。
除了要求,我们还需要实现一个AuthorizationHandler:

 public class LastNameHandler : AuthorizationHandler<IAuthorizationRequirement>{protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, IAuthorizationRequirement requirement){var lastNameRequirement = requirement as LastNamRequirement;if (lastNameRequirement == null){return Task.CompletedTask;}var isTeacher = context.User.HasClaim((c) =>{return c.Type == System.Security.Claims.ClaimTypes.Role && c.Value == "老师";});var isWang = context.User.HasClaim((c) =>{return c.Type == "LastName" && c.Value == lastNameRequirement.LastName;});if (isTeacher && isWang){context.Succeed(requirement);}return Task.CompletedTask;}}

AuthorizationHandler是一个抽象类,继承它后需要重写其中的HandleRequirementAsync方法。这里才是真正判断是否授权成功的地方。要求(Requirement)跟用户的声明(Claim)信息会被传到这方法里,然后我们根据这些信息进行判断,如果符合授权就调用context.Succeed方法。这里注意如果不符合请谨慎调用context.Failed方法,因为策略之间一般是OR的关系,这个策略不通过,可能有其他策略通过
在ConfigureServices方法中添加策略跟注册AuthorizationHandler到DI容器中:

services.AddSingleton<IAuthorizationHandler, LastNameHandler>();
services.AddAuthorization(options =>{options.AddPolicy("王老师", policy =>policy.AddRequirements(new LastNamRequirement { LastName = "王" }));});

使用AddSingleton生命周期来注册LastNameHandler,这个生命周期并不一定要单例,看情况而定。在AddAuthorization中添加一个策略叫"王老师"。这里有个个人认为比较怪的地方,为什么AuthorizationHandler不是在AddAuthorization方法中配置?而是仅仅注册到容器中就可以开始工作了。如果有一个需求,仅仅是需要自己调用一下自定义的AuthorizationHandler,而并不想它真正参与授权。这样的话就不能使用DI的方式来获取实例了,因为一注册进去就会参与授权的校验了。
在TeacherController下添加一个 Edit Action:

  [Authorize(Policy="王老师")]
public IActionResult Edit()
{return Content("Edit success");
}

给AuthorizeAttribute的Policy设置为“王老师”。
修改Login方法添加一个姓的声明:

  [HttpPost]public async Task<IActionResult> Login([FromForm]string userName, [FromForm]string password){//validate username passwordvar claims = new List<Claim>{new Claim(ClaimTypes.Name, userName),new Claim(ClaimTypes.Role, "老师"),new Claim("LastName", "王"),};var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme,new ClaimsPrincipal(claimsIdentity));return Redirect("/");}

运行一下程序,访问一下/teacher/edit,可以看到访问成功了。如果修改Login方法,修改LastName的声明为其他值,则访问会拒绝。

使用泛型Func方法配置策略

如果你的策略比较简单,其实还有个更简单的方法来配置,就是在AddAuthorization方法内直接使用一个Func来配置策略。使用Func来配置一个女老师的策略:

 options.AddPolicy("女老师", policy =>policy.RequireAssertion((context) =>{var isTeacher = context.User.HasClaim((c) =>{return c.Type == System.Security.Claims.ClaimTypes.Role && c.Value == "老师";});var isFemale = context.User.HasClaim((c) =>{return c.Type == "Sex" && c.Value == "女";});return isTeacher && isFemale;})
);

总结

1.Authentication跟Authorization是两个不同的概念。Authentication是指认证,认证用户的身份;Authorization是授权,判断是否有某个功能的权限。2.Authorization内置了基于角色的授权策略。3.可以使用自定义AuthorizationHandler跟Func的方式来实现自定义策略。

吐槽

关于认证跟授权微软为我们考虑了很多很多,包括identityserver,基本上能想到的都有了,什么oauth,openid,jwt等等。其实本人是不太喜欢用的。虽然微软都给你写好了,考虑很周到,但是学习跟Trouble shooting都是要成本的。其实使用中间件、过滤器再配合redis等组件,很容易自己实现一套授权认证方案,自由度也更高,有问题修起来也更快。自己实现一下也可以更深入的了解某项的技术,比如jwt是如果工作的,oauth是如何工作的,这样其实更有意义。

关注我的公众号一起玩转技术

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/306083.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

android dialog 自定义布局,如何设置AlertDialog的自定义布局?

调用我的对话框:alertDialog showInfoDialog(message "$wrongPasscodeMessage\n$retryMessage")方法如下:fun FragmentActivity.showInfoDialog(message: String?): AlertDialog? {return try {val customLayout layoutInflater.inflate(R.layout.custom_layout…

android 打开谷歌导航,国内开启google位置记录功能/android版google maps 7+上,恢复位置记录功能在国内使用(需root)...

android版google 地图在 7以后的版本上&#xff0c;位置记录功能在国内不能用了&#xff0c;提示本功能不能在中国使用。至少对本人&#xff0c;“位置记录”功能是非常有用的功能&#xff0c;尤其是骑车出行时记录自己的路线。目前还没找到替代产品。之前一段时间内恢复回旧版…

程序员过关斩将--少年派登录安全的奇幻遐想

“据说&#xff0c;这篇也是快餐&#xff0c;完全符合年轻人口味说到登录&#xff0c;无人不知无人不晓。每一个有用户体系的相关系统都会有登录的入口&#xff0c;登录是为了确认操作人的正确性。说到登录安全&#xff0c;其实是一个很伟大的命题&#xff0c;不过常用的手段也…

C# 9 新特性 —— 增强的 foreach

C# 9 新特性 —— 增强的 foreachIntro在 C# 9 中增强了 foreach 的使用&#xff0c;使得一切对象都有 foreach 的可能我们来看一段代码&#xff0c;这里我们试图遍历一个 int 类型的值思考一下&#xff0c;我们可以怎么做使得上面的代码编译通过呢&#xff1f;迭代器模式迭代器…

android系统休眠发广播,Android - BroadcastReceiver

BroadcastReceiverBroadcastReceiver&#xff0c;广播接收者&#xff0c;用来接收系统和应用的广播&#xff0c;并做出相应的处理&#xff0c;如电量过低时提示用户充电等&#xff1b;BroadcastReceiver 是 Android 的四大组件之一&#xff0c;分为 普通广播、有序广播、粘性广…

开源·共享·创新|2020年中国.NET开发者大会圆满收官!

“疫情无限续费”的2020年&#xff0c;对于14亿中国人而言&#xff0c;是必须习惯口罩长在来脸上的一年&#xff1b;是各种线下聚会&#xff0c;被迫数次延期、滞后、云上举办的一年&#xff1b;……而对于潜心修行&#xff0c;静蓄能量的中国.NET开发者而言&#xff0c;2020绝…

android+百度lbs云,百度——LBS.云 v2.0——云存储扩展字段——Android

今天要解决两个问题&#xff1a;1云存储扩展字段2上传的数据是乱码3android版本上传数据到云端使用了一段时间LBS云功能之后&#xff0c;随着对系统的熟悉&#xff0c;默认提供的字段&#xff0c;肯定无法满足需要。比如增加注释&#xff0c;价格&#xff0c;档次等字段的时候。…

年终将至,回顾我们一起走过的 2020

又到了年终末尾匆匆忙忙的 2020 似乎按下了倍速键一晃眼我们就从夏天走到了冬天在这不平凡的一年中我们同途共进也笑着成长让我们跟随着六大年度词条重温这一年我们共同经历的值得骄傲的瞬间吧&#xff01;点击文内高亮部分&#xff0c;阅读文章了解更多人才“倍”出星桥计划出…

灵魂拷问:你和大佬,技术差距有多大?

今天咱们聊点技术以外的内容。前几天&#xff0c;有程序员在某个坛子上发帖吐槽&#xff0c;新来的应届生张嘴就是分布式&#xff0c;一堆框架&#xff0c;可代码根本不会写。马上有人跟贴说自己也遇到过这种情况&#xff0c;说之前自己遇到过一个应届生&#xff0c;开口闭口动…

达梦数据查询编码_查询数据库的编码方式

在Mysql中(1)查看Mysql数据库编码show variables like character_set_database 或者 show create database 数据库名称(2)查看Mysql中某张表的编码show create table 表名show create database 数据库名称、show create table 表名 &#xff0c;还能够显示建库和建表语句。(3)…

玩转git-flow工作流-分支解析

概述搞开发的相信大部分人git天天都在用&#xff0c;那么一般我们在实际工程当中&#xff0c;遵循一个合理、清晰的Git使用流程&#xff0c;是非常重要的。否则&#xff0c;每个人都提交一堆杂乱无章的commit&#xff0c;项目很快就会变得难以协调和维护。那么是如何来规范整个…

android中的帧动画,[Android开发] Android中的帧动画

MainActivity文件&#xff1a;public class MainActivity extends Activity implements OnClickListener{AnimationDrawable anim_draw;SuppressLint("NewApi")Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);set…

ksu7对讲机调频软件_数字对讲机的群呼功能原理是什么?你了解多少?

大家都明白数字对讲机能够达到组呼、群呼、选呼的用途&#xff0c;但对数字对讲机的群呼功能原理可能操作方并不是太熟悉&#xff0c;下面我们就和大家来谈谈有关数字对讲机的群呼功能原理&#xff1a;无线对讲机群呼是为了更好地达到1个数字对讲机能够同一时间跟多个数字对讲机…

引入Jaeger——使用

上一篇定义了两种使用Jaeger的方式&#xff1a;中间件和action过滤器&#xff0c;下面这个例子定义了两个服务 WebAPI01&#xff0c;请求WebAPI02&#xff0c;采用的是中间件的请求方式。引入JaegerSharp包&#xff08;或发布到自己的Nuget库里引用&#xff09;WebAPI01的Start…

抖音ai智能机器人挂机_电销秘诀 电销企业难以拒绝的AI智能电销机器人

眼下是快节奏的时代&#xff0c;超智能化的电销机器人已然成为了电销企业实现高速发展的首选方式。为何现代电销企业都会摒弃纯人工电销方式&#xff0c;采取机器人与人工协作方式呢&#xff1f;这就不得不说是因为AI外呼机器人的卓越性能和优势。一&#xff0c;提升工作效率AI…

dataset的去重计数 g2_ExcelExcel去重、计数一步到位,这个方法简单到哭

私信回复关键词【插件】&#xff0c;获取Excel高手都在用的“插件合集插件使用小技巧”&#xff01;最近在哼哧哼哧搬家&#xff0c;搬家第一天&#xff0c;面对空荡荡的房子&#xff0c;我发现了一个严峻的问题——日用品还没买。我打开了一个月前写下的日用品清单&#xff1a…

【源码解读】Vue与ASP.NET Core WebAPI的集成

在前面博文【Vue】Vue 与 ASP.NET Core WebAPI 的集成中&#xff0c;介绍了集成原理&#xff1a;在中间件管道中注册SPA终端中间件&#xff0c;整个注册过程中&#xff0c;终端中间件会调用node&#xff0c;执行npm start命令启动vue开发服务器&#xff0c;向中间件管道添加路由…

ios采用什么技术_app软件公司开发宠物别APP采用什么技术?

app软件公司开发宠物别APP采用什么技术&#xff1f;随着经济的发展&#xff0c;人们生活水平的提高&#xff0c;养宠的家庭越来越多&#xff0c;宠物也逐渐成为主人家庭成员的重要组成部分&#xff0c;宠物识别APP在市场上也是很热门的手机软件&#xff0c;那么它是根据什么原理…

Dapr微服务应用开发系列3:服务调用构件块

题记&#xff1a;这篇开始逐一深入介绍各个构件块&#xff0c;从服务调用开始原理所谓服务调用&#xff0c;就是通过这个构件块让你方便的通过HTTP或者gRPC协议同步调用其他服务的方法&#xff0c;这些方法也是通过HTTP或者gRPC来暴露的。而方便的含义在于&#xff0c;你无需担…

Android开发p图软件,媲美大神P图效果 Android软件抠图神手

媲美大神P图效果 Android软件抠图神手2013年02月20日 01:50作者&#xff1a;杨霏霏编辑&#xff1a;杨霏霏文章出处&#xff1a;泡泡网原创分享泡泡网手机频道2月20日 PS的功能大家耳熟能详&#xff0c;其中抠图便是各位PS用户普遍会用到的一个功能。然而手机上抠图大家想过吗&…