ASP.NET Core Authentication and Authorization

来源:https://www.cnblogs.com/kklldog/archive/2020/04/02/auth-in-aspnetcore.html
-Advertisement-
Play Games

最近把一個Asp .net core 2.0的項目遷移到Asp .net core 3.1,項目啟動的時候直接報錯: 看意思是缺少了一個authorization的中間件,這個項目在Asp.net core 2.0上是沒問題的。 startup是這樣註冊的: 查了文檔後發現3.0的示例代碼多了一個U ...


最近把一個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 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 == "老師";
            });
            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 == "老師";
            });
            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是如何工作的,這樣其實更有意義。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 作者:網易雲 鏈接:https://www.zhihu.com/question/27696290/answer/381993207 來源:知乎 著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。 什麼是大數據 近幾年,市場上出現了很多和大數據相關的崗位,不管是數據分析、數據挖掘, ...
  • 大家好,我是小棧君,因為個人和工作的緣故,所以拖更了一點時間,但是關於拖更的內容小棧君會在後續的時間中補回來,還希望大家繼續支持和關註小棧君。當然,在國內疫情稍微減緩的情況下,小棧君在這裡也多說兩句,在非常時刻,我們應當保持警惕,清洗手,多通風,避免人群聚集,希望大家平安健康, 閑話不多說,我們直接 ...
  • 前文總結了NIO的內容,有了NIO的一些基礎之後,我們就可以來看下Netty。Netty是Java領域的高性能網路傳輸框架,RPC的技術核心就是網路傳輸和序列化,所以Netty給予了RPC在網路傳輸領域巨大的支持。 一個簡單的Netty代碼實現 網路傳輸基於的是TCP協議,所以會有服務端和客戶端之分 ...
  • "OpenWrite 博客群發" 第一步:前往 "七牛雲" 官方註冊賬號 第二步:在“個人中心”中完成賬號實名認證 第三步:添加對象存儲 第四步:輸入存儲空間名稱,並將訪問控制設置為“公開” 第五步:在密鑰管理中創建AccessKey和SecretKey 第六步:在openwrite的高級配置中,圖 ...
  • volatile是Java虛擬機提供的 輕量級 的同步機制(“乞丐版”的synchronized) 1. 保證可見性 2. 不保證原子性 3. 禁止指令重排 可見性 指當多個線程訪問同一個變數時,如果其中一個線程修改了這個變數的值,其他線程能夠立即看得到修改的值 驗證可見性demo: 結果: 不保證 ...
  • 1、一張表,裡面有 ID 自增主鍵,當 insert 了 17 條記錄之後,刪除了第 15,16,17 條記錄,再把 Mysql 重啟,再 insert 一條記錄,這條記錄的 ID 是 18 還是 15 ? (1)如果表的類型是 MyISAM,那麼是 18因為 MyISAM 表會把自增主鍵的最大 I ...
  • 1. 構造函數 1.1. 構造函數特征 與類相同名稱,不聲明返回值類型。 不能被static、final、synchronized、abstract、native修飾,不能有return語句。 1.2. 構造函數作用 創建對象,給對象進行初始化。 1.3. 構造函數分類 隱式無參構造器,顯示構造器。 ...
  • 摘要:本文實例講述了php非同步多線程swoole用法。分享給大家供大家參考。具體分析如下:swoole重新定義PHP語言的高性能網路通信框架,提供了PHP語言的非同步多線程服務,下麵的實例就可以證實這一功能。一般來說,Swoole提供了PHP語言的非同步多線程伺服器,非同步TCP/UDP網路客戶端,非同步M ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...