主頁 > .NET開發 > ASP.NET Core Authentication and Authorization

ASP.NET Core Authentication and Authorization

2020-09-15 18:23:54 .NET開發

最近把一個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=https://www.cnblogs.com/kklldog/p/"administrator" />
        

密碼:

從前臺傳入用戶名密碼后進行用戶名密碼校驗(示例代碼省略了密碼校驗),如果合法,則把用戶的基本資訊存到一個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=https://www.cnblogs.com/kklldog/p/"/Student/index">/Student/index

/Teacher/Index

/Student/Edit

退出

改一下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 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("/");
        }

新建一個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 =https://www.cnblogs.com/kklldog/p/= "老師";
            });
            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 password

            var 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 =https://www.cnblogs.com/kklldog/p/= "老師";
            });
            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是如何作業的,這樣其實更有意義,

關注我的公眾號一起玩轉技術

轉載請註明出處,本文鏈接:https://www.uj5u.com/net/49205.html

標籤:.NET Core

上一篇:我們是怎么實作gRPC CodeFirst-生成proto

下一篇:給 EF Core 查詢增加 With NoLock

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • WebAPI簡介

    Web體系結構: 有三個核心:資源(resource),URL(統一資源識別符號)和表示 他們的關系是這樣的:一個資源由一個URL進行標識,HTTP客戶端使用URL定位資源,表示是從資源回傳資料,媒體型別是資源回傳的資料格式。 接下來我們說下HTTP. HTTP協議的系統是一種無狀態的方式,使用請求/ ......

    uj5u.com 2020-09-09 22:07:47 more
  • asp.net core 3.1 入口:Program.cs中的Main函式

    本文分析Program.cs 中Main()函式中代碼的運行順序分析asp.net core程式的啟動,重點不是剖析原始碼,而是理清程式開始時執行的順序。到呼叫了哪些實體,哪些法方。asp.net core 3.1 的程式入口在專案Program.cs檔案里,如下。ususing System; us ......

    uj5u.com 2020-09-09 22:07:49 more
  • asp.net網站作為websocket服務端的應用該如何寫

    最近被websocket的一個問題困擾了很久,有一個需求是在web網站中搭建websocket服務。客戶端通過網頁與服務器建立連接,然后服務器根據ip給客戶端網頁發送資訊。 其實,這個需求并不難,只是剛開始對websocket的內容不太了解。上網搜索了一下,有通過asp.net core 實作的、有 ......

    uj5u.com 2020-09-09 22:08:02 more
  • ASP.NET 開源匯入匯出庫Magicodes.IE Docker中使用

    Magicodes.IE在Docker中使用 更新歷史 2019.02.13 【Nuget】版本更新到2.0.2 【匯入】修復單列匯入的Bug,單元測驗“OneColumnImporter_Test”。問題見(https://github.com/dotnetcore/Magicodes.IE/is ......

    uj5u.com 2020-09-09 22:08:05 more
  • 在webform中使用ajax

    如果你用過Asp.net webform, 說明你也算是.NET 開發的老兵了。WEBform應該是2011 2013左右,當時還用visual studio 2005、 visual studio 2008。后來基本都用的是MVC。 如果是新開發的專案,估計沒人會用webform技術。但是有些舊版 ......

    uj5u.com 2020-09-09 22:08:50 more
  • iis添加asp.net網站,訪問提示:由于擴展配置問題而無法提供您請求的

    今天在iis服務器配置asp.net網站,遇到一個問題,記錄一下: 問題:由于擴展配置問題而無法提供您請求的頁面。如果該頁面是腳本,請添加處理程式。如果應下載檔案,請添加 MIME 映射。 WindowServer2012服務器,添加角色安裝完.netframework和iis之后,運行aspx頁面 ......

    uj5u.com 2020-09-09 22:10:00 more
  • WebAPI-處理架構

    帶著問題去思考,大家好! 問題1:HTTP請求和回傳相應的HTTP回應資訊之間發生了什么? 1:首先是最底層,托管層,位于WebAPI和底層HTTP堆疊之間 2:其次是 訊息處理程式管道層,這里比如日志和快取。OWIN的參考是將訊息處理程式管道的一些功能下移到堆疊下端的OWIN中間件了。 3:控制器處理 ......

    uj5u.com 2020-09-09 22:11:13 more
  • 微信門戶開發框架-使用指導說明書

    微信門戶應用管理系統,采用基于 MVC + Bootstrap + Ajax + Enterprise Library的技術路線,界面層采用Boostrap + Metronic組合的前端框架,資料訪問層支持Oracle、SQLServer、MySQL、PostgreSQL等資料庫。框架以MVC5,... ......

    uj5u.com 2020-09-09 22:15:18 more
  • WebAPI-HTTP編程模型

    帶著問題去思考,大家好!它是什么?它包含什么?它能干什么? 訊息 HTTP編程模型的核心就是訊息抽象,表示為:HttPRequestMessage,HttpResponseMessage.用于客戶端和服務端之間交換請求和回應訊息。 HttpMethod類包含了一組靜態屬性: private stat ......

    uj5u.com 2020-09-09 22:15:23 more
  • 部署WebApi隨筆

    一、跨域 NuGet參考Microsoft.AspNet.WebApi.Cors WebApiConfig.cs中配置: // Web API 配置和服務 config.EnableCors(new EnableCorsAttribute("*", "*", "*")); 二、清除默認回傳XML格式 ......

    uj5u.com 2020-09-09 22:15:48 more
最新发布
  • C#多執行緒學習(二) 如何操縱一個執行緒

    <a href="https://www.cnblogs.com/x-zhi/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/2943582/20220801082530.png" alt="" /></...

    uj5u.com 2023-04-19 09:17:20 more
  • C#多執行緒學習(二) 如何操縱一個執行緒

    C#多執行緒學習(二) 如何操縱一個執行緒 執行緒學習第一篇:C#多執行緒學習(一) 多執行緒的相關概念 下面我們就動手來創建一個執行緒,使用Thread類創建執行緒時,只需提供執行緒入口即可。(執行緒入口使程式知道該讓這個執行緒干什么事) 在C#中,執行緒入口是通過ThreadStart代理(delegate)來提供的 ......

    uj5u.com 2023-04-19 09:16:49 more
  • 記一次 .NET某醫療器械清洗系統 卡死分析

    <a href="https://www.cnblogs.com/huangxincheng/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/214741/20200614104537.png" alt="" /&g...

    uj5u.com 2023-04-18 08:39:04 more
  • 記一次 .NET某醫療器械清洗系統 卡死分析

    一:背景 1. 講故事 前段時間協助訓練營里的一位朋友分析了一個程式卡死的問題,回過頭來看這個案例比較經典,這篇稍微整理一下供后來者少踩坑吧。 二:WinDbg 分析 1. 為什么會卡死 因為是表單程式,理所當然就是看主執行緒此時正在做什么? 可以用 ~0s ; k 看一下便知。 0:000> k # ......

    uj5u.com 2023-04-18 08:33:10 more
  • SignalR, No Connection with that ID,IIS

    <a href="https://www.cnblogs.com/smartstar/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/u36196.jpg" alt="" /></a>...

    uj5u.com 2023-03-30 17:21:52 more
  • 一次對pool的誤用導致的.net頻繁gc的診斷分析

    <a href="https://www.cnblogs.com/dotnet-diagnostic/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/3115652/20230225090434.png" alt=""...

    uj5u.com 2023-03-28 10:15:33 more
  • 一次對pool的誤用導致的.net頻繁gc的診斷分析

    <a href="https://www.cnblogs.com/dotnet-diagnostic/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/3115652/20230225090434.png" alt=""...

    uj5u.com 2023-03-28 10:13:31 more
  • C#遍歷指定檔案夾中所有檔案的3種方法

    <a href="https://www.cnblogs.com/xbhp/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/957602/20230310105611.png" alt="" /></a&...

    uj5u.com 2023-03-27 14:46:55 more
  • C#/VB.NET:如何將PDF轉為PDF/A

    <a href="https://www.cnblogs.com/Carina-baby/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/2859233/20220427162558.png" alt="" />...

    uj5u.com 2023-03-27 14:46:35 more
  • 武裝你的WEBAPI-OData聚合查詢

    <a href="https://www.cnblogs.com/podolski/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/616093/20140323000327.png" alt="" /><...

    uj5u.com 2023-03-27 14:46:16 more