asp.net core 鑒權授權


.Net6 鑒權授權

什么是鑒權、授權?

驗證 和 授權 二者常被混淆。驗證 關心的是用戶登錄與否,而 授權 關心“用戶在登錄 能否做某些事”。你可以認為 驗證 是在問:“我知道這個用戶是誰嗎?”,而 授權 問的是:“這個用戶有權限做某件事嗎?”

鑒權與授權是有區別的,舉個例子。一天周末,小明去游樂場玩。首先,向工作人員出示入場門票,工作人員驗證門票的有效期,門票是否偽造…驗證通過后小明進入游樂園,這時小明入園后想去玩過山車,可過山車是VIP門票才能玩的,設備管理員查看了小明的門票,發現小明的門票是普通游客,所以沒法玩過山車。

鑒權:工作人員驗證門票的過程 = 鑒權。
授權:小明不是VIP,沒有玩過山車的權限,這就是授權。

利用上面一個例子對應到我們一個請求的過程:
小明 = 一個請求
門票 = token
檢票員 = 鑒權中間件
設備管理員 = 授權中間件
過山車 = 受保護的資源

那么將上面的例子轉換為一次http請求過程的話就是,請求到達鑒權中間件,鑒權中間件驗證后是否驗證通過,不通過,則直接返回未授權,通過后到達授權中間件,授權中間件查看是否有權限訪問該資源,有則通過,否則返回權限不足。

  • 鑒權:身份驗證 知道用戶的身份。 例如,Alice 使用用戶名和密碼登錄,並且服務器使用密碼對 Alice 進行身份驗證。
  • 授權 :是指是否允許用戶執行某個操作。 例如,Alice 有權獲取資源,但不能創建資源。

名詞理解

Claims(證件單元)

大家應該都知道身份證長什么樣子的,如下:

img

其中,姓名:奧巴馬;性別:男;民族:肯尼亞;出生:1961.08.04,等等這些身份信息,可以看出都是一個一個的鍵值對,那如果我們想在程序中存這些東西,怎么樣來設計呢?對,你可能想到了使用一個字典進行存儲,一個Key,一個Value剛好滿足需求。但是Key,Value的話感覺不太友好,不太面向對象,所以如果我們做成一個對象的話,是不是更好一些呢?最起碼你可以用vs的智能提示了吧,我們修改一下,改成下面這樣:

//我給對象取一個名字叫`Claim`你沒有意見吧
public class Claim
{
    public string ClaimType { get; set; }

    public string ClaimValue { get; set; }
}

ClaimType 就是Key,ClaimValue就代表一個Value。這樣的話,剛好可以存儲一個鍵值對。這時候姓名:奧巴馬是不是可以存進去了。

微軟的人很貼心,給我們准備了一些默認的ClaimType呢?很多常用的都在里面呢,一起看看吧:

這里延伸第一個知識點:ClaimTypes

img

為了閱讀體驗,截圖我只放了一部分哦。可以看到有什么Name,Email,Gender,MobilePhone等常用的都已經有了,其他的還有很多。細心的讀者可能注意了,它的命名空間是System.Security.Claims,那就說明這個東西是.net 框架的一部分,嗯,我們暫時只需要知道這么多就OK了。

Claim 介紹完畢,是不是很簡單,其他地方怎么翻譯我不管,在本篇文章里面,它叫 “證件單元”。

ClaimsIdentity(身份證)

在有了“證件單元”之后,我們就用它可以制造一張身份證了,那么應該怎么樣制造呢?有些同學可能已經想到了,對,就是新建一個對象,然后在構造函數里面把身份證單元傳輸進去,然后就得到一張身份證了。我們給這張身份證取一個英文名字叫 “ClaimsIdentity”,這個名字看起來還蠻符合的,既有 Claims 表示其組成部分,又有表示其用途的 Identity(身份),很滿意的一個名字。

實際上,在現實生活中,我們的身份證有一部分信息是隱藏的,有一部分是可以直接看到的。比如新一代的身份證里面存儲了你的指紋信息你是看不到的,這些都存儲在身份證里面的芯片中,那能看到的比如姓名啊,年齡啊等。我們在設計一個對象的時候也是一樣,需要暴露出來一些東西,那這里我們的 ClaimsIdentity 就暴露出來一個 Name,Lable等。

我們造的身份證(ClaimsIdentity)還有一個重要的屬性就是類型(AuthenticationType),等等,AuthenticationType是什么東西?看起來有點眼熟的樣子。我們知道我們自己的身份證是干嘛的吧,就是用來證明我們的身份的,在你證明身份出示它的時候,其實它有很多種形式載體的,什么意思呢?比如你可以直接拿出實體形式的身份證,那也可以是紙張形式的復印件,也可以是電子形式的電子碼等等,這個時候就需要有一個能夠表示其存在形式的類型字段,對,這個AuthenticationType就是干這個事情的。

然后我們在給我們的身份證添加一些潤色,讓其看起來好看,比如提供一些方法添加 Claims 的,刪除 Claims的,寫到二進制流里面的啊等等,最終我們的身份證對象看起來基本上是這樣了:

public class ClaimsIdentity
{
    public ClaimsIdentity(IEnumerable<Claim> claims){}
    
    //名字這么重要,當然不能讓別人隨便改啊,所以我不許 set,除了我兒子跟我姓,所以是 virtual 的
    public virtual string Name { get; }
    public string Label { get; set; }
    
    //這是我的證件類型,也很重要,同樣不許 set
    public virtual string AuthenticationType { get; }
    
    public virtual void AddClaim(Claim claim);
    
    public virtual void RemoveClaim(Claim claim);
    
    public virtual void FindClaim(Claim claim);
}

嗯,到這里,我們的身份證看起來似乎很完美了,但是從面向對象的角度來說好像還少了點什么東西? 對~,還是抽象,我們需要抽象出來一個接口來進行一些約束,約束什么呢?既然作為一個證件,那么肯定會涉及到這幾個屬性信息:
1、名字。2、類型。3、證件是否合法。
反應到接口里面的話就是如下,我們給接口取個名字叫:“身份(IIdentity)”:

這里延伸第二個知識點:IIdentity接口。

// 定義證件對象的基本功能。
public interface IIdentity
{
    //證件名稱
    string Name { get; }
    
    // 用於標識證件的載體類型。
    string AuthenticationType { get; }
    
    //是否是合法的證件。
    bool IsAuthenticated { get; }
}

所以我們的 ClaimsIdentity 最終看起來定義就是這樣的了:

public class ClaimsIdentity : IIdentity
{
    //......
}

ClaimsIdentity 介紹完畢,是不是發現也很簡單,其他地方怎么翻譯我不管,在本篇文章里面,它叫 “身份證”。

ClaimsPrincipal(證件當事人)

有了身份證,我們就能證明我就是我了,有些時候一個人有很多張身份證,你猜這個人是干嘛的? 對,不是黃牛就是詐騙犯。

但是,有些時候一個人還有其他很多種身份,你猜這個人是干嘛的?這就很正常了對不對,比如你可以同時是一名教師,母親,商人。如果你想證明你同時有這幾種身份的時候,你可能需要出示教師證,你孩子的出生證,法人代表的營業執照證。

在程序中,一個身份證不僅僅代表你這個人了,而是代表一個身份,是證明你自己的主要身份哦。如果一個人還有其他很多種身份,這個時候就需要有一個東西(載體)來攜帶着這些證件了對吧?OK,我們給需要攜帶證件的這個對象取一個貼切點的名字,叫“證件當事人(ClaimsPrincipal)”吧。

以下是 Principal 這個單詞在詞典給出的解釋,我用它你應該沒意見吧:

principal  ['prɪnsəpl]  
adj. 主要的;資本的
n. 首長;校長;資本;當事人

這個時候可能有同學會問了,是不是應該叫ClaimsIdentityPrincipal比較好呢?嗯,我也覺得應該叫 ClaimsIdentityPrincipal 可能更好一點,或許微軟的人偷懶了,簡寫成了ClaimsPrincipal

知道其功能后,代碼就很好寫了,和上面ClaimsIdentity一樣的套路:

public class ClaimsPrincipal 
{
    //把擁有的證件都給當事人
    public ClaimsPrincipal(IEnumerable<ClaimsIdentity> identities){}
    
    //當事人的主身份呢
    public virtual IIdentity Identity { get; }
    
    public virtual IEnumerable<ClaimsIdentity> Identities { get; }
    
    public virtual void AddIdentity(ClaimsIdentity identity);
    
    //為什么沒有RemoveIdentity , 留給大家思考吧?
}

當時人看起來也幾乎完美了,但是我們還需要對其抽象一下,抽象哪些東西呢? 作為一個當事人,你應該有一個主身份吧,就是你的身份證咯,可能你還會用到角色(角色后面會詳細介紹,這里你知道有這么個東西就行了)。

這里延伸第三個知識點:IPrincipal 接口。

public interface IPrincipal
{
    //身份
    IIdentity Identity { get; }
    
    //在否屬於某個角色
    bool IsInRole(string role);
}

然后,我們的 證件當事人 看起來應該是這樣的:

public class ClaimsPrincipal : IPrincipal 
{
   //...
}

ClaimsPrincipal 介紹完了,也很簡單吧? 其他地方怎么翻譯我不管,在本篇文章里面,它叫 “證件當事人”。

想在,我們已經知道了 “證件單元(Claims)” , “身份證(ClaimsIdentity)” , “證件當事人(ClaimsPrincipal)”,並且整理清楚了他們之間的邏輯關系,趁熱打鐵,下面這個圖是一個identity登入部分的不完全示意圖,虛線圈出來的部分應該可以看懂了吧:

img

可以看出,首先我們在app這邊有一些證件單元,然后調用ClaimsIdentity把證件單元初始化為一個身份證,然后再把身份證交給證件當事人由其保管。

Scheme(方案)

音譯:方案,簡單了說就是使用什么方式來驗證身份,常見的有cookie、jwt web token,這里主要使用jwt講解

Authentication(鑒權) Authorization(授權)

鑒權的英文單詞是:Authentication
授權的英文單詞是:Authorization
我用的是聯想記憶法。首先,找它們之間的區別,對我而言最直觀的是鑒權的單詞里包含en,而授權的單詞包含or。而鑒權又叫身份認證,認的拼音里也包含en,所以單詞里有en的就是鑒權,而另一個就是授權。

鑒權:鑒定身份,有沒有登錄,你是誰
授權:判定有沒有權限

名詞匯總

Claim:信息

ClaimsIdentity:身份

ClaimsPrincipal:一個人可以有多個身份

Scheme:方案

Policy:政策:規則,必須滿足某一個規則,比如必須使用qq郵箱或者手機號登錄

Role:角色:其實就是一種Policy,是一種封裝好的Policy,兩者都是規則

AuthenticationSchemes:鑒權:讀取用戶身份信息,不同位置,不同解析方式,例如從URL,Sersion等讀取,不同的位置讀取就是不同的Schemes。指定用戶rr信息的來源,可能多個來源的合集里面去篩選(JWT的角色+Cokkie里面的Age)。

AuthenticationTicket:用戶票據

Authentication:鑒權:鑒定身份,有沒有登錄,你是誰

Authorization:授權:判定有沒有權限

  1. 登錄寫入憑證
  2. 鑒權就是找出用戶
  3. 授權就是判斷權限
  4. 退出就是清理憑證

授權:AddAuthorization=》就是判斷能不能訪問,規則+數據來源;

Authorize特性:

  1. Policy:策略-規則,確定什么條件
  2. Roles:角色—就是一個封裝好的Policy,也是規則
  3. AuthenticationSchemes:指定用戶信息的來源,可能多個來源的合集里面去篩選(JWT的角色+Cookie的Age)

其中Policy和Roles指定的是規則,AuthenticationSchemes指定來源.

鑒權:如果使用鑒權,必須打開UseAuthentication中間件,鑒權就是解析用戶信息的,解析完成以后放在base HttpContex.User里面

  1. 添加中間件
app.UseAuthentication();
  1. IOC容器注入,只有AddAuthentication是不行的,只有容器注冊,里面沒有指定使用什么靠什么方式解析用戶信息.
services.AddAuthentication(options =>
{
   options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie();
//指定一個方案,並使用Cookie的方式解析

ClaimsIDentity :解析出來的客戶信息放進來,返回給客戶端.

Claim:鍵值對,ClaimTypes類型,string Value.,字典一個key 一個value

  1. base.httpcontext.SignInasync把 ClaimIdentity包裝到ClaimsPrincipal里面返回給客戶端.

基本流程

  1. UseAuthentication 表示使用這個中間件
  2. AddAuthentication必須指定默認Scheme,且AddCookie
service.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)//要授權
	   .AddCookie();//使用Cookie的方式	   

ClaimsIdentity:后台讀取的,暴漏給客戶端的信息,是個對象集合,對象就是Key-Value

微服務授權中心

默認的Swagger里面看不到post 生成Token,所以使用postman測試,需要注意的

直接在網關層配置JWT+配置文件,通過網關訪問微服務的時候就可以實現鑒權授權,但如果對方直接調用微服務本身的鏈接還是可以不需要授權直接訪問的,所以更加細節化的授權策略還是需要在微服務內部手動來寫。

網關層基本校驗

網關層

  1. 配置文件 配置微服務是否需要鑒權
 "AuthenticationOptions": {
   "AuthenticationProviderKey": "UserGatewayKey",
   "AllowedScopes": []
 },
  1. 注入鑒權服務
var authenticationProviderKey = "UserGatewayKey";
builder.Services.AddAuthentication("Bearer")
   .AddIdentityServerAuthentication(authenticationProviderKey, options =>
   {
       options.Authority = "http://localhost:7200";
       options.ApiName = "UserApi";
       options.RequireHttpsMetadata = false;
       options.SupportedTokens = SupportedTokens.Both;
   });
  1. 一個服務里面有的需要授權,有的需要授權-配置多個路由就可以了
    1. 一個帶鑒權的路由
    2. 一個不帶鑒權的路由
  2. 復雜策略policy,5個角色,下端需要用戶信息等
  3. 綜合來說,其實生產環境下,網關只負責鑒權和有效性,但是用戶信息獲取 授權檢測 服務實例自己完成的。

網關對服務的作用域角色進行校驗

  1. 配置文件 配置微服務是否需要鑒權 作用域 角色
"AuthenticationOptions": {
  "AuthenticationProviderKey": "UserGatewayKey",
  "AllowedScopes": [ "UserWebAPIService", "UserMinimalAPIService" ]
},
"RouteClaimsRequirement": {
  "Role": "Assistant"
},
  1. 微服務內部
var claims = new[]
{
       new Claim(ClaimTypes.Name, userModel.Name),
       new Claim("scope","UserWebAPIService"),//為了微服務Scope,必須小寫
       new Claim("Role","Assistant"),//這個不能默認角色授權,動態角色授權
       new Claim("EMail", userModel.EMail),
       new Claim("Account", userModel.Account),
       new Claim("Age", userModel.Age.ToString()),
       new Claim("Id", userModel.Id.ToString()),
       new Claim("Mobile", userModel.Mobile),
       new Claim(ClaimTypes.Role,userModel.Role),
       //new Claim("Role", userModel.Role),//這個不能角色授權
       new Claim("Sex", userModel.Sex.ToString())//各種信息拼裝
};

網關內Scope大范圍的作用域檢測還是推薦的,但是Claim這種細節的建議下端自己處理。

鑒權授權

  1. 注入服務,告訴鑒權授權用什么策略,都校驗那些信息
  2. 添加鑒權授權中間件app.UseAuthentication();解析Token app.UseAuthorization();
  3. 給對應的api添加鑒權特性即可

通過網關進行鑒權授權

  1. 創建鑒權中心Common.AuthenticationCenter
    1. 通過鑒權中心生成有效的JWT Token
  2. 配置網關鑒權Common.OcelotGateway
    1. 注入JWT服務,什么策略 校驗什么,沒有使用app.UseAuthentication();
      app.UseAuthorization();中間件的原因是因為app.UseOcelot().Wait();內置的有。
    2. 添加JWT配置信息
    3. 調整Ocelot里面微服務的配置文件,增加鑒權授權
#region JWT檢驗 HS

JWTTokenOptions tokenOptions = new JWTTokenOptions();
builder.Configuration.Bind("JWTTokenOptions", tokenOptions);
string authenticationProviderKey = "UserGatewayKey";

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)//Bearer Scheme
       .AddJwtBearer(authenticationProviderKey, options =>
       {
           options.TokenValidationParameters = new TokenValidationParameters
           {
               //JWT有一些默認的屬性,就是給鑒權時就可以篩選了
               ValidateIssuer = true,//是否驗證Issuer
               ValidateAudience = true,//是否驗證Audience
               ValidateLifetime = true,//是否驗證失效時間---默認還添加了300s后才過期
               ClockSkew = TimeSpan.FromSeconds(0),//token過期后立馬過期
               ValidateIssuerSigningKey = true,//是否驗證SecurityKey
               ValidAudience = tokenOptions.Audience,//Audience,需要跟前面簽發jwt的設置一致
               ValidIssuer = tokenOptions.Issuer,//Issuer,這兩項和前面簽發jwt的設置一致
               IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(tokenOptions.SecurityKey)),//拿到SecurityKey
           };
       });
#endregion JWT檢驗 HS
  "JWTTokenOptions": {
    "Audience": "http://localhost:8761",
    "Issuer": "http://localhost:8761",
    "SecurityKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDI2a2EJ7m872v0afyoSDJT2o1+SitIeJSWtLJU8/Wz2m7gStexajkeD+Lka6DSTy8gt9UwfgVQo6uKjVLG5Ex7PiGOODVqAEghBuS7JzIYU5RvI543nNDAPfnJsas96mSA7L/mD7RTE2drj6hf3oZjJpMPZUQI/B1Qjb5H3K3PNwIDAQAB"
  }
 "AuthenticationOptions": {
        "AuthenticationProviderKey": "UserGatewayKey",
        "AllowedScopes": []
      }

網關自己的權限驗證

通過httpAPi動態修改配置文件

網關內置的identityServer4

  1. nuget Ocelot.Administration
  2. IOC注入
builder.Services.AddOcelot()
    .AddCustomLoadBalancer<CustomPollingLoadBalancer>(loadBalancerFactoryFunc)
    .AddConsul()
    .AddPolly()
     //.AddConfigStoredInConsul()
     .AddCacheManager(x =>
     {
         x.WithDictionaryHandle();//默認字典存儲
     })
     .AddSingletonDefinedAggregator<CustomUserAggregator>()
     .AddAdministration("/administration", "ElevenSecret")//前面是路徑--后面是秘鑰
  1. 可以通過鏈接獲取或者修改配置文件
  2. administration 可以清理緩存,動態修改配置文件

原文鏈接


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM