日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > asp.net >内容正文

asp.net

快速理解ASP.NET Core的认证与授权

發(fā)布時間:2023/12/4 asp.net 25 豆豆
生活随笔 收集整理的這篇文章主要介紹了 快速理解ASP.NET Core的认证与授权 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

ASP.NET Core的認(rèn)證與授權(quán)已經(jīng)不是什么新鮮事了,微軟官方的文檔對于如何在ASP.NET Core中實現(xiàn)認(rèn)證與授權(quán)有著非常詳細(xì)深入的介紹。但有時候在開發(fā)過程中,我們也往往會感覺無從下手,或者由于一開始沒有進(jìn)行認(rèn)證授權(quán)機制的設(shè)計與規(guī)劃,使得后期出現(xiàn)一些混亂的情況。這里我就嘗試結(jié)合一個實際的例子,從0到1來介紹ASP.NET Core中如何實現(xiàn)自己的認(rèn)證與授權(quán)機制。

當(dāng)我們使用Visual Studio自帶的ASP.NET Core Web API項目模板新建一個項目的時候,Visual Studio會問我們是否需要啟用認(rèn)證機制,如果你選擇了啟用,那么Visual Studio會在項目創(chuàng)建的時候,加入一些輔助依賴和一些輔助類,比如加入對Entity Framework以及ASP.NET Identity的依賴,以幫助你實現(xiàn)基于Entity Framework和ASP.NET Identity的身份認(rèn)證。如果你還沒有了解過ASP.NET Core的認(rèn)證與授權(quán)的一些基礎(chǔ)內(nèi)容,那么當(dāng)你打開這個由Visual Studio自動創(chuàng)建的項目的時候,肯定會一頭霧水,不知從何開始,你甚至?xí)岩勺詣觿?chuàng)建的項目中,真的是所有的類或者方法都是必須的嗎?所以,為了讓本文更加簡單易懂,我們還是選擇不啟用身份認(rèn)證,直接創(chuàng)建一個最簡單的ASP.NET Core Web API應(yīng)用程序,以便后續(xù)的介紹。

新建一個ASP.NET Core Web API應(yīng)用程序,這里我是在Linux下使用JetBrains Rider新建的項目,也可以使用標(biāo)準(zhǔn)的Visual Studio或者VSCode來創(chuàng)建項目。創(chuàng)建完成后,運行程序,然后使用瀏覽器訪問/WeatherForecast端點,就可以獲得一組隨機生成的天氣及溫度數(shù)據(jù)的數(shù)組。你也可以使用下面的curl命令來訪問這個API:

1

curl -X GET "http://localhost:5000/WeatherForecast" -H? "accept: text/plain"

現(xiàn)在讓我們在WeatherForecastController的Get方法上設(shè)置一個斷點,重新啟動程序,仍然發(fā)送上述請求以命中斷點,此時我們比較關(guān)心User對象的狀態(tài),打開監(jiān)視器查看User對象的屬性,發(fā)現(xiàn)它的IsAuthenticated屬性為false:

在很多情況下,我們可能并不需要在Controller的方法中獲取認(rèn)證用戶的信息,因此也從來不會關(guān)注User對象是否真的處于已被認(rèn)證的狀態(tài)。但是當(dāng)API需要根據(jù)用戶的某些信息來執(zhí)行一些特殊邏輯時,我們就需要在這里讓User的認(rèn)證信息處于一種合理的狀態(tài):它是已被認(rèn)證的,并且包含API所需的信息。這就是本文所要討論的ASP.NET Core的認(rèn)證與授權(quán)。

認(rèn)證

應(yīng)用程序?qū)τ谑褂谜叩纳矸菡J(rèn)定包含兩部分:認(rèn)證授權(quán)。認(rèn)證是指當(dāng)前用戶是否是系統(tǒng)的合法用戶,而授權(quán)則是指定合法用戶對于哪些系統(tǒng)資源具有怎樣的訪問權(quán)限。我們先來看如何實現(xiàn)認(rèn)證。

在此,我們單說由ASP.NET Core應(yīng)用程序本身實現(xiàn)的認(rèn)證,不討論具有統(tǒng)一Identity Provider完成身份認(rèn)證的情況(比如單點登錄),這樣的話就能夠更加清晰地了解ASP.NET Core本身的認(rèn)證機制。接下來,我們嘗試在ASP.NET Core應(yīng)用程序上,實現(xiàn)Basic認(rèn)證。

Basic認(rèn)證需要將用戶的認(rèn)證信息附屬在HTTP請求的Authorization的頭(Header)上,認(rèn)證信息是一串由用戶名和密碼通過BASE64編碼后所產(chǎn)生的字符串,例如,當(dāng)你采用Basic認(rèn)證,并使用daxnet和password作為訪問WeatherForecast API的用戶名和密碼時,你可能需要使用下面的命令行來調(diào)用WeatherForecast:

1

curl -X GET "http://localhost:5000/WeatherForecast" -H? "accept: text/plain" -H "Authorization: Basic ZGF4bmV0OnBhc3N3b3Jk"

在ASP.NET Core Web API中,當(dāng)應(yīng)用程序接收到上述請求后,就會從Request的Header里讀取Authorization的信息,然后BASE64解碼得到用戶名和密碼,然后訪問數(shù)據(jù)庫來確認(rèn)所提供的用戶名和密碼是否合法,以判斷認(rèn)證是否成功。這部分工作通常可以采用ASP.NET Core Identity框架來實現(xiàn),不過在這里,為了能夠更加清晰地了解認(rèn)證的整個過程,我們選擇自己動手來實現(xiàn)。

首先,我們定義一個User對象,并且預(yù)先設(shè)計好幾個用戶,以便模擬存儲用戶信息的數(shù)據(jù)庫,這個User對象的代碼如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

public class User

{

????public string UserName { get; set; }

????public string Password { get; set; }

????public IEnumerable<string> Roles { get; set; }

????public int Age { get; set; }

????public override string ToString() => UserName;

????public static readonly User[] AllUsers = {

????????new User

????????{

????????????UserName = "daxnet", Password = "password", Age = 16, Roles = new[] { "admin", "super_admin" }

????????},

????????new User

????????{

????????????UserName = "admin", Password = "admin", Age = 29, Roles = new[] { "admin" }

????????}

????};

}

該User對象包括用戶名、密碼以及它的角色名稱,不過暫時我們不需要關(guān)心角色信息。User對象還包含一個靜態(tài)字段,我們將它作為用戶信息數(shù)據(jù)庫來使用。

接下來,在應(yīng)用程序中添加一個AuthenticationHandler,用來獲取Request Header中的用戶信息,并對用戶信息進(jìn)行驗證,代碼如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

public class BasicAuthenticationHandler : AuthenticationHandler<BasicAuthenticationSchemeOptions>

{

????public BasicAuthenticationHandler(

????????IOptionsMonitor<BasicAuthenticationSchemeOptions> options,

????????ILoggerFactory logger,

????????UrlEncoder encoder,

????????ISystemClock clock) : base(options, logger, encoder, clock)

????{

????}

????protected override Task<AuthenticateResult> HandleAuthenticateAsync()

????{

????????if (!Request.Headers.ContainsKey("Authorization"))

????????{

????????????return Task.FromResult(AuthenticateResult.Fail("Authorization header is not specified."));

????????}

????????var authHeader = Request.Headers["Authorization"].ToString();

????????if (!authHeader.StartsWith("Basic "))

????????{

????????????return Task.FromResult(

????????????????AuthenticateResult.Fail("Authorization header value is not in a correct format"));

????????}

????????var base64EncodedValue = authHeader["Basic ".Length..];

????????var userNamePassword = Encoding.UTF8.GetString(Convert.FromBase64String(base64EncodedValue));

????????var userName = userNamePassword.Split(':')[0];

????????var password = userNamePassword.Split(':')[1];

????????var user = User.AllUsers.FirstOrDefault(u => u.UserName == userName && u.Password == password);

????????if (user == null)

????????{

????????????return Task.FromResult(AuthenticateResult.Fail("Invalid username or password."));

????????}

????????var claims = new[]

????????{

????????????new Claim(ClaimTypes.NameIdentifier, user.UserName),

????????????new Claim(ClaimTypes.Role, string.Join(',', user.Roles)),

????????????new Claim(ClaimTypes.UserData, user.Age.ToString())

????????};

????????var claimsPrincipal =

????????????new ClaimsPrincipal(new ClaimsIdentity(

????????????????claims,

????????????????"Basic",

????????????????ClaimTypes.NameIdentifier, ClaimTypes.Role));

????????var ticket = new AuthenticationTicket(claimsPrincipal, new AuthenticationProperties

????????{

????????????IsPersistent = false

????????}, "Basic");

????????return Task.FromResult(AuthenticateResult.Success(ticket));

????}

}

在上面的HandleAuthenticateAsync代碼中,首先對Request Header進(jìn)行合法性校驗,比如是否包含Authorization的Header,以及Authorization Header的值是否合法,然后,將Authorization Header的值解析出來,通過Base64解碼后得到用戶名和密碼,與用戶信息數(shù)據(jù)庫里的記錄進(jìn)行匹配,找到匹配的用戶。接下來,基于找到的用戶對象,創(chuàng)建ClaimsPrincipal,并基于ClaimsPrincipal創(chuàng)建AuthenticationTicket然后返回。

這段代碼中有幾點值得關(guān)注:

  • BasicAuthenticationSchemeOptions本身只是一個繼承于AuthenticationSchemeOptions的POCO類。AuthenticationSchemeOptions類通常是為了向AuthenticationHandler提供一些輸入?yún)?shù)。比如,在某個自定義的用戶認(rèn)證邏輯中,可能需要通過環(huán)境變量讀入字符串解密的密鑰信息,此時就可以在這個自定義的AuthenticationSchemeOptions中增加一個Passphrase的屬性,然后在Startup.cs中,通過service.AddScheme調(diào)用將從環(huán)境變量中讀取的Passphrase的值傳入

  • 除了將用戶名作為Identity Claim加入到ClaimsPrincipal中之外,我們還將用戶的角色(Role)用逗號串聯(lián)起來,作為Role Claim添加到ClaimsPrincipal中,目前我們暫時不需要涉及角色相關(guān)的內(nèi)容,但是先將這部分代碼放在這里以備后用。另外,我們將用戶的年齡(Age)放在UserData claim中,在實際中應(yīng)該是在用戶對象上有該用戶的出生日期,這樣比較合理,然后這個出生日期應(yīng)該放在DateOfBirth claim中,這里為了簡單起見,就先放在UserData中了

  • ClaimsPrincipal的構(gòu)造函數(shù)中,可以指定哪個Claim類型可被用作用戶名稱,而哪個Claim類型又可被用作用戶的角色。例如上面代碼中,我們選擇NameIdentifier類型作為用戶名,而Role類型作為用戶角色,于是,在接下來的Controller代碼中,由NameIdentifier這種Claim所指向的字符串值,就會被看成用戶名而被綁定到Identity.Name屬性上

  • 回過頭來看看BasicAuthenticationSchemeOptions類,它的實現(xiàn)非常簡單:

    1

    2

    3

    4

    public class BasicAuthenticationSchemeOptions : AuthenticationSchemeOptions

    {

    }

    接下來,在Startup.cs文件里,修改ConfigureServices和Configure方法,加入Authentication的支持:

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    public void ConfigureServices(IServiceCollection services)

    {

    ????services.AddControllers();

    ????services.AddSwaggerGen(c =>

    ????{

    ????????c.SwaggerDoc("v1", new OpenApiInfo { Title = "WebAPIAuthSample", Version = "v1" });

    ????});

    ????services.AddAuthentication("Basic")

    ????????.AddScheme<BasicAuthenticationSchemeOptions, BasicAuthenticationHandler>(

    ????????????"Basic", options => { });

    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)

    {

    ????if (env.IsDevelopment())

    ????{

    ????????app.UseDeveloperExceptionPage();

    ????????app.UseSwagger();

    ????????app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "WebAPIAuthSample v1"));

    ????}

    ????app.UseHttpsRedirection();

    ????app.UseRouting();

    ????app.UseAuthentication();

    ????app.UseEndpoints(endpoints => { endpoints.MapControllers(); });

    }

    現(xiàn)在,運行應(yīng)用程序,在WeatherForecastController的Get方法上設(shè)置斷點,然后執(zhí)行上面的curl命令,當(dāng)斷點被命中時,觀察this.User對象可以發(fā)現(xiàn),IsAuthenticated屬性變?yōu)榱藅rue,Name屬性也被設(shè)置為用戶名:

    大多數(shù)身份認(rèn)證框架會提供一些輔助方法來幫助開發(fā)人員將AuthenticationHandler注冊到應(yīng)用程序中,例如,基于JWT持有者身份認(rèn)證的框架會提供一個AddJwtBearer的方法,將JWT身份認(rèn)證機制加入到應(yīng)用程序中,它本質(zhì)上也是調(diào)用AddScheme方法來完成AuthenticationHandler的注冊。在這里,我們也可以自定義一個AddBasicAuthentication的擴(kuò)展方法:

    1

    2

    3

    4

    5

    6

    7

    public static class Extensions

    {

    ????public static AuthenticationBuilder AddBasicAuthentication(this AuthenticationBuilder builder)

    ????????=> builder.AddScheme<BasicAuthenticationSchemeOptions, BasicAuthenticationHandler>(

    ????????????"Basic",

    ????????????options => { });

    }

    然后修改Starup.cs文件,將ConfigureServices方法改為下面這個樣子:

    1

    2

    3

    4

    5

    6

    7

    8

    9

    public void ConfigureServices(IServiceCollection services)

    {

    ????services.AddControllers();

    ????services.AddSwaggerGen(c =>

    ????{

    ????????c.SwaggerDoc("v1", new OpenApiInfo { Title = "WebAPIAuthSample", Version = "v1" });

    ????});

    ????services.AddAuthentication("Basic").AddBasicAuthentication();

    }

    這樣做的好處是,你可以為開發(fā)人員提供更多比較有針對性的配置認(rèn)證機制的編程接口,這對于一個認(rèn)證模塊/框架的開發(fā)是一個很好的設(shè)計。

    在curl命令中,如果我們沒有指定Authorization Header,或者Authorization Header的值不正確,那么WeatherForecast API仍然可以被調(diào)用,只不過IsAuthenticated屬性為false,也無法從this.User對象得到用戶信息。其實,阻止未認(rèn)證用戶訪問API并不是認(rèn)證的事情,API被未認(rèn)證(或者說未登錄)用戶訪問也是合理的事情,因此,要實現(xiàn)對于未認(rèn)證用戶的訪問限制,就需要進(jìn)一步實現(xiàn)ASP.NET Core Web API的另一個安全控制組件:授權(quán)。

    授權(quán)

    認(rèn)證相比,授權(quán)的邏輯會比較復(fù)雜:認(rèn)證更多是技術(shù)層面的事情,而授權(quán)則更多地與業(yè)務(wù)相關(guān)。市面上常見的認(rèn)證機制頂多也就是那么幾種或者十幾種,而授權(quán)的方式則是多樣化的,因為不同app不同業(yè)務(wù),對于app資源訪問的授權(quán)需求是不同的。最為常見的一種授權(quán)方式就是RBAC(Role Based Access Control,基于角色的訪問控制),它定義了什么樣的角色對于什么資源具有怎樣的訪問權(quán)限。在RBAC中,不同的用戶都被賦予了不同的角色,而為了管理方便,又為具有相同資源訪問權(quán)限的用戶設(shè)計了用戶組,而將訪問控制設(shè)置在用戶組上,更進(jìn)一步,組和組之間還可以有父子關(guān)系。

    請注意上面的黑體字,每一個黑體標(biāo)注的詞語都是授權(quán)相關(guān)的概念,在ASP.NET Core中,每一個授權(quán)需求(Authorization Requirement)對應(yīng)一個實現(xiàn)IAuthorizationRequirement的類,并由AuthorizationHandler負(fù)責(zé)處理相應(yīng)的授權(quán)邏輯。簡單地理解,授權(quán)需求表示什么樣的用戶才能夠滿足被授權(quán)的要求,或者說什么樣的用戶才能夠通過授權(quán)去訪問資源。一個授權(quán)需求往往僅定義并處理一種特定的授權(quán)邏輯,ASP.NET Core允許將多個授權(quán)需求組合成授權(quán)策略(Authorization Policy)然后應(yīng)用到被訪問的資源上,這樣的設(shè)計可以保證授權(quán)需求的設(shè)計與實現(xiàn)都是小粒度的,從而分離不同授權(quán)需求的關(guān)注點。在授權(quán)策略的層面,通過組合不同授權(quán)需求從而達(dá)到靈活實現(xiàn)授權(quán)業(yè)務(wù)的目的。

    比如:假設(shè)app中有的API只允許管理員訪問,而有的API只允許滿18周歲的用戶訪問,而另外的一些API需要用戶既是超級管理員又滿18歲。那么就可以定義兩種Authorization Requirement:GreaterThan18Requirement和SuperAdminRequirement,然后設(shè)計三種Policy:第一種只包含GreaterThan18Requirement,第二種只包含SuperAdminRequirement,第三種則同時包含這兩種Requirement,最后將這些不同的Policy應(yīng)用到不同的API上就可以了。

    回到我們的案例代碼,首先定義兩個Requirement:SuperAdminRequirement和GreaterThan18Requirement:

    1

    2

    3

    4

    5

    6

    public class SuperAdminRequirement : IAuthorizationRequirement

    {

    }

    public class GreaterThan18Requirement : IAuthorizationRequirement

    {

    }

    然后分別實現(xiàn)SuperAdminAuthorizationHandle和GreaterThan18AuthorizationHandler:



    實現(xiàn)邏輯也非常清晰:在GreaterThan18AuthorizationHandler中,通過UserData claim獲得年齡信息,如果年齡大于18,則授權(quán)成功;在SuperAdminAuthorizationHandler中,通過Role claim獲得用戶所處的角色,如果角色中包含super_admin,則授權(quán)成功。接下來就需要將這兩個Requirement加到所需的Policy中,然后注冊到應(yīng)用程序里:

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    25

    26

    27

    public void ConfigureServices(IServiceCollection services)

    {

    ????services.AddControllers();

    ????services.AddSwaggerGen(c =>

    ????{

    ????????c.SwaggerDoc("v1", new OpenApiInfo { Title = "WebAPIAuthSample", Version = "v1" });

    ????});

    ????services.AddAuthentication("Basic").AddBasicAuthentication();

    ????services.AddAuthorization(options =>

    ????{

    ????????options.AddPolicy("AgeMustBeGreaterThan18", builder =>

    ????????{

    ????????????builder.Requirements.Add(new GreaterThan18Requirement());

    ????????});

    ????????options.AddPolicy("UserMustBeSuperAdmin", builder =>

    ????????{

    ????????????builder.Requirements.Add(new SuperAdminRequirement());

    ????????});

    ????});

    ????services.AddSingleton<IAuthorizationHandler, GreaterThan18AuthorizationHandler>();

    ????services.AddSingleton<IAuthorizationHandler, SuperAdminAuthorizationHandler>();

    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)

    {

    ????if (env.IsDevelopment())

    ????{

    ????????app.UseDeveloperExceptionPage();

    ????????app.UseSwagger();

    ????????app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "WebAPIAuthSample v1"));

    ????}

    ????app.UseHttpsRedirection();

    ????app.UseRouting();

    ????app.UseAuthentication();

    ????app.UseAuthorization();

    ????app.UseEndpoints(endpoints => { endpoints.MapControllers(); });

    }

    在ConfigureServices方法中,我們定義了兩種Policy:AgeMustBeGreaterThan18和UserMustBeSuperAdmin,最后,在API Controller或者Action上,應(yīng)用AuthorizeAttribute,從而指定所需的Policy即可。比如,如果希望WeatherForecase API只有年齡大于18歲的用戶才能訪問,那么就可以這樣做:

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    [HttpGet]

    [Authorize(Policy = "AgeMustBeGreaterThan18")]

    public IEnumerable<WeatherForecast> Get()

    {

    ????var rng = new Random();

    ????return Enumerable.Range(1, 5).Select(index => new WeatherForecast

    ????????{

    ????????????Date = DateTime.Now.AddDays(index),

    ????????????TemperatureC = rng.Next(-20, 55),

    ????????????Summary = Summaries[rng.Next(Summaries.Length)]

    ????????})

    ????????.ToArray();

    }

    運行程序,假設(shè)有三個用戶:daxnet、admin和foo,它們的BASE64認(rèn)證信息分別為:

    • daxnet:ZGF4bmV0OnBhc3N3b3Jk

    • admin:YWRtaW46YWRtaW4=

    • foo:Zm9vOmJhcg==

    那么,相同的curl命令,指定不同的用戶認(rèn)證信息時,得到的結(jié)果是不一樣的:

    daxnet用戶年齡小于18歲,所以訪問API不成功,服務(wù)端返回403:

    admin用戶滿足年齡大于18歲的條件,所以可以成功訪問API:

    而foo用戶本身沒有在系統(tǒng)中注冊,所以服務(wù)端返回401,表示用戶沒有認(rèn)證成功:

    小結(jié)

    本文簡要介紹了ASP.NET Core中用戶身份認(rèn)證與授權(quán)的基本實現(xiàn)方法,幫助初學(xué)者或者需要使用這些功能的開發(fā)人員快速理解這部分內(nèi)容。ASP.NET Core的認(rèn)證與授權(quán)體系非常靈活,能夠集成各種不同的認(rèn)證機制與授權(quán)方式,文章也無法進(jìn)行全面詳細(xì)的介紹。不過無論何種框架哪種實現(xiàn),它的實現(xiàn)基礎(chǔ)也就是本文所介紹的這些內(nèi)容,如果打算自己開發(fā)一套認(rèn)證和授權(quán)的框架,也可以參考本文。

    總結(jié)

    以上是生活随笔為你收集整理的快速理解ASP.NET Core的认证与授权的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。