原文作者:老張的哲學
如何給接口實現權限驗證?
其實關於這一塊,我思考了下,因爲畢竟我的項目中是使用的vue + api 搭建一個前臺展示,大部分頁面都沒有涉及到權限驗證,本來要忽略這一章節,可是猶豫再三,還是給大家簡單分析了下,個人還是希望陪大家一直搭建一個較爲強大的,只要是涉及到後端那一定就需要 登錄=》驗證了,本文主要是參考網友https://www.cnblogs.com/RayWang/p/9255093.html的思路,我自己稍加改動,大家都可以看看。
根據維基百科定義,JWT(讀作 [/dʒɒt/]),即JSON Web Tokens,是一種基於JSON的、用於在網絡上聲明某種主張的令牌(token)。JWT通常由三部分組成: 頭信息(header), 消息體(payload)和簽名(signature)。它是一種用於雙方之間傳遞安全信息的表述性聲明規範。JWT作爲一個開放的標準(RFC 7519),定義了一種簡潔的、自包含的方法,從而使通信雙方實現以JSON對象的形式安全的傳遞信息。
以上是JWT的官方解釋,可以看出JWT並不是一種只能權限驗證的工具,而是一種標準化的數據傳輸規範。所以,只要是在系統之間需要傳輸簡短但卻需要一定安全等級的數據時,都可以使用JWT規範來傳輸。規範是不因平臺而受限制的,這也是JWT做爲授權驗證可以跨平臺的原因。
如果理解還是有困難的話,我們可以拿JWT和JSON類比:
JSON是一種輕量級的數據交換格式,是一種數據層次結構規範。它並不是只用來給接口傳遞數據的工具,只要有層級結構的數據都可以使用JSON來存儲和表示。當然,JSON也是跨平臺的,不管是Win還是Linux,.NET還是Java,都可以使用它作爲數據傳輸形式。
1)客戶端向授權服務系統發起請求,申請獲取“令牌”。
2)授權服務根據用戶身份,生成一張專屬“令牌”,並將該“令牌”以JWT規範返回給客戶端
3)客戶端將獲取到的“令牌”放到http請求的headers中後,向主服務系統發起請求。主服務系統收到請求後會從headers中獲取“令牌”,並從“令牌”中解析出該用戶的身份權限,然後做出相應的處理(同意或拒絕返回資源)
零.生成Token令牌
關於JWT授權,其實過程是很簡單的,大家其實這個時候靜下心想一想就能明白,這個就是四步走:
首先我們需要一個具有一定規則的 Token 令牌,也就是 JWT 令牌(比如我們的公司門禁卡),//登錄
然後呢,我們再定義哪些地方需要什麼樣的角色(比如領導辦公室我們是沒辦法進去的),//授權機制
接下來,整個公司需要定一個規則,就是如何對這個 Token 進行驗證,不能隨便寫個字條,這樣容易被造假(比如我們公司門上的每一道刷卡機),//認證方案
最後,就是安全部門,開啓認證中間件服務(那這個服務可以關閉的,比如我們電影裏看到的黑客會把這個服務給關掉,這樣整個公司安保就形同虛設了)。//開啓中間件
那現在我們就是需要一個具有一定規則的 Token 令牌,大家可以參考:
這個實體類就是用來生成 Token 的,代碼記錄如下:
using Blog.Core.API.Utils;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Text;
namespace Blog.Core.API.AuthHelper.OverWrite
{
/// <summary>
/// 頒發Jwt字符串
/// </summary>
public class JwtHelper
{
public static string IssueJwt(TokenModelJwt tokenModel)
{
string iss = Appsettings.app(new string[] { "Audience", "Issuer" });
string aud = Appsettings.app(new string[] { "Audience", "Audience" });
string secret = Appsettings.app(new string[] { "Audience", "Secret" });
var claims = new List<Claim>
{
/*
* 特別重要:
1、這裏將用戶的部分信息,比如 uid 存到了Claim 中,如果你想知道如何在其他地方將這個 uid從 Token 中取出來,請看下邊的SerializeJwt() 方法,或者在整個解決方案,搜索這個方法,看哪裏使用了!
2、你也可以研究下 HttpContext.User.Claims ,具體的你可以看看 Policys/PermissionHandler.cs 類中是如何使用的。
*/
new Claim(JwtRegisteredClaimNames.Jti,tokenModel.Uid.ToString()),
new Claim(JwtRegisteredClaimNames.Iat,$"{new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds()}"),
new Claim(JwtRegisteredClaimNames.Nbf,$"{new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds()}") ,
// 這個就是過期時間,目前是過期1000秒,可自定義,注意JWT有自己的緩衝過期時間
new Claim (JwtRegisteredClaimNames.Exp,$"{new DateTimeOffset(DateTime.Now.AddSeconds(1000)).ToUnixTimeSeconds()}"),
new Claim(JwtRegisteredClaimNames.Iss,iss),
new Claim(JwtRegisteredClaimNames.Aud,aud),
};
// 可以將一個用戶的多個角色全部賦予;
// 作者:DX 提供技術支持;
claims.AddRange(tokenModel.Role.Split(',').Select(s => new Claim(ClaimTypes.Role, s)));
//祕鑰 (SymmetricSecurityKey 對安全性的要求,密鑰的長度太短會報出異常)
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secret));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var jwt = new JwtSecurityToken(
issuer: iss,
claims: claims,
signingCredentials: creds);
var jwtHandler = new JwtSecurityTokenHandler();
var encodedJwt = jwtHandler.WriteToken(jwt);
return encodedJwt;
}
/// <summary>
/// 解析
/// </summary>
/// <param name="jwtStr"></param>
/// <returns></returns>
public static TokenModelJwt SerilaizeJwt(string jwtStr)
{
var jwtHandler = new JwtSecurityTokenHandler();
JwtSecurityToken jwtToken = new JwtSecurityToken(jwtStr);
object role;
try
{
jwtToken.Payload.TryGetValue(ClaimTypes.Role, out role);
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
var tm = new TokenModelJwt
{
Uid = (jwtToken.Id).ObjToInt(),
Role = role != null ? role.ObjToString() : "",
};
return tm;
}
}
/// <summary>
/// 令牌
/// </summary>
public class TokenModelJwt
{
/// <summary>
/// Id
/// </summary>
public long Uid { get; set; }
/// <summary>
/// 角色
/// </summary>
public string Role { get; set; }
/// <summary>
/// 職能
/// </summary>
public string Work { get; set; }
}
}
Appsettings.cs
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace Blog.Core.API.Utils
{
public class Appsettings
{
static IConfiguration Configuration { get; set; }
static Appsettings()
{
string Path = "appsettings.json";
Configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.Add(new JsonConfigurationSource
{
Path = Path,
Optional = false,
ReloadOnChange = true
})
.Build();// 這樣的話,可以直接讀目錄裏的json文件,而不是 bin 文件夾下的,所以不用修改複製屬性
}
/// <summary>
/// 封裝要操作的字符串
/// </summary>
/// <param name="sections">節點</param>
/// <returns>最後一個節點的值</returns>
public static string app(params string[] sections)
{
try
{
var val = string.Empty;
for(int i = 0;i < sections.Length; i++)
{
val += sections[i] + ":";
}
return Configuration[val.TrimEnd(':')];
}catch(Exception e)
{
return "";
}
}
}
}
這個接口如何調用呢,很簡單,就是我們的登錄api:
/// <summary>
/// 登陸
/// </summary>
/// <param name="name">賬號</param>
/// <param name="pass">密碼</param>
/// <returns></returns>
[HttpPost]
public async Task<IActionResult> GetJwtStr(string name, string pass)
{
string jwtStr = string.Empty;
bool suc = false;
// 獲取用戶的角色名,請暫時忽略其內部是如何獲取的,可以直接用 var userRole="Admin"; 來代替更好理解。
var userRole = "admin";
if (userRole != null)
{
// 將用戶id和角色名,作爲單獨的自定義變量封裝進 token 字符串中。
TokenModelJwt tokenModel = new TokenModelJwt { Uid = 1, Role = userRole };
// 登錄,獲取到一定規則的 Token 令牌
jwtStr = JwtHelper.IssueJwt(tokenModel);
suc = true;
}
else
{
jwtStr = "login fail!";
}
return Ok(new
{
success = suc,
token = jwtStr,
});
}
現在我們獲取到Token了,那如何進行授權認證呢,彆着急,重頭戲馬上到來!
一、JWT授權認證流程——自定義中間件
在之前的搭建中,swagger已經基本成型,其實其功能之多,不是我這三篇所能寫完的,想要添加權限,先從服務開始
0、Swagger中開啓JWT服務
我們要測試 JWT 授權認證,就必定要輸入 Token令牌,那怎麼輸入呢,平時的話,我們可以使用 Postman 來控制輸入,就是在請求的時候,在 Header 中,添加Authorization屬性,
但是我們現在使用了 Swagger 作爲接口文檔,那怎麼輸入呢,彆着急, Swagger 已經幫我們實現了這個錄入 Token令牌的功能:
在ConfigureServices -> AddSwaggerGen 服務中,增加以下===...之間的代碼,注意是swagger服務內部:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
#region Swagger
services.AddSwaggerGen(c => {
c.SwaggerDoc("v1",new Info {
Version = "v0.1.0",
Title = "Blog.Core API",
Description = "框架說明文檔",
TermsOfService = "None",
Contact = new Contact
{
Name = "Blog.Core",
Email = "[email protected]",
Url = "https://www.jianshu.com/u/4198eba0fbdd?_wv=1031"
}
});
// 獲取運行時根目錄
var basePath = Microsoft.DotNet.PlatformAbstractions.ApplicationEnvironment.ApplicationBasePath;
// 這個就是剛剛配置的xml文件名
var xmlPath = Path.Combine(basePath,"Blog.Core.API.xml");
// 默認的第二個參數是false,這個是controller的註釋,記得修改
c.IncludeXmlComments(xmlPath, true);
// 這個就是Model層的xml文件名
var xmlModelPath = Path.Combine(basePath,"Blog.Core.Model.xml");
c.IncludeXmlComments(xmlModelPath);
=====================================
#region Token綁定到ConfigureServices
// 添加Header驗證消息
// c.OperationFilter<SwaggerHeader>();
var security = new Dictionary<string, IEnumerable<string>> {{ "Blog.Core",new string[]{ } },};
c.AddSecurityRequirement(security);
// 方案名稱“Blog.Core”可自定義,上下一致即可
c.AddSecurityDefinition("Blog.Core",new ApiKeyScheme {
Description = "JWT授權(數據將在請求頭中進行傳輸) 直接在下框中輸入Bearer {token}(注意兩者之間是一個空格)\"",
Name = "Authorization", // jwt默認的參數名稱
In = "header", // jwt默認存放Authorization信息的位置(請求頭中)
Type = "apiKey"
});
#endregion
=====================================
});
#endregion
}
然後執行代碼,就可以在 swagger/index.html 頁面裏看到這個Token入口了:
image.png
大家點開,看到輸入框,在輸入Token的時候,需要在Token令牌的前邊加上Bearer (爲什麼要加這個,下文會說明,請一定要注意看,一定要明白爲啥要帶,因爲它涉及到了什麼是授權,什麼是認證,還要自定義認證中間件還是官方認證中間件的區別,請注意看下文),比如是這樣的:
image.png
但是請注意!如果你使用的是中間件 app.UseMiddleware<JwtTokenAuth>() ,要是使用 Bearer xxxx傳值的時候,記得在中間件的方法中,把Token的 “Bearer 空格” 字符給截取掉,這樣的:
1:API接口授權策略
這裏可以直接在api接口上,直接設置該接口所對應的角色權限信息:
image.png
這個時候我們就需要對每一個接口設置對應的 Roles 信息,但是如果我們的接口需要對應多個角色的時候,我們就可以直接寫多個:
image.png
這裏有一個情況,如果角色多的話,不僅不利於我們閱讀,還可能在配置的時候少一兩個role,比如這個 api接口1 少了一個 system 的角色,再比如那個 api接口2 把 Admin 角色寫成了 Adnin 這種不必要的錯誤,真是很難受,那怎麼辦呢,欸!這個時候就出現了基於策略的授權機制:
我們在 ConfigureService 中可以這麼設置:
// 1【授權】、這個和上邊的異曲同工,好處就是不用在controller中,寫多個 roles 。
// 然後這麼寫 [Authorize(Policy = "Admin")]
services.AddAuthorization(options =>
{
options.AddPolicy("Client", policy => policy.RequireRole("Client").Build());
options.AddPolicy("Admin", policy => policy.RequireRole("Admin").Build());
options.AddPolicy("SystemOrAdmin", policy => policy.RequireRole("Admin", "System"));
});
這樣的話,我們只需要在 controller 或者 action 上,直接寫策略名就可以了:
[HttpGet]
[Authorize(Policy = "SystemOrAdmin")]
public ActionResult<IEnumerable<string>> Get()
{
return new string[] { "value1", "value2" };
}
這樣我們的第一步就完成了。繼續走第二步,身份驗證方案。
關於授權認證有兩種方式,可以使用官方的認證方式,也可以使用自定義中間件的方法,具體請往下看,咱們先說說如何進行自定義認證。
2、自定義認證之身份驗證設置
上邊第一步中,咱們已經對每一個接口api設置好了 授權機制 ,那這裏就要開始認證,咱們先看看如何實現自定義的認證:
JwtTokenAuth,一箇中間件,用來過濾每一個http請求,就是每當一個用戶發送請求的時候,都先走這一步,然後再去訪問http請求的接口
JwtTokenAuth.cs
using Blog.Core.API.AuthHelper.OverWrite;
using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
using System.Security.Claims;
using System.Threading.Tasks;
namespace Blog.Core.API.AuthHelper
{
public class JwtTokenAuth
{
// 中間件一定要有一個next,將管道可以正常的走下去
private readonly RequestDelegate _next;
public JwtTokenAuth(RequestDelegate next)
{
_next = next;
}
public Task Invoke(HttpContext httpContext)
{
// 檢測是否包含'Authorization'請求頭
if (!httpContext.Request.Headers.ContainsKey("Authorization"))
{
return _next(httpContext);
}
var tokenHeader = httpContext.Request.Headers["Authorization"].ToString().Replace("Bearer ", "");
try
{
if (tokenHeader.Length >= 128)
{
TokenModelJwt tm = JwtHelper.SerializeJwt(tokenHeader);
// 授權 Claim 關鍵
var claimList = new List<Claim>();
var claim = new Claim(ClaimTypes.Role, tm.Role);
claimList.Add(claim);
var identity = new ClaimsIdentity(claimList);
var principal = new ClaimsPrincipal(identity);
httpContext.User = principal;
}
}
catch (Exception e)
{
Console.WriteLine($"{DateTime.Now} middleware wrong:{e.Message}");
}
return _next(httpContext);
}
}
}
MiddlewareHelpers.cs
using Microsoft.AspNetCore.Builder;
namespace Blog.Core.API.AuthHelper
{
// 這裏定義一箇中間件Helper,主要作用就是給當前模塊的中間件取一個別名
public static class MiddlewareHelpers
{
public static IApplicationBuilder UseJwtTokenAuth(this IApplicationBuilder app)
{
return app.UseMiddleware<JwtTokenAuth>();
}
}
}
前兩步咱們都完成了,從授權到自定義身份驗證方案,就剩下最後一步,開啓中間件了。
3:開啓自定義認證中間件,實現Http信道攔截
這個很簡單,只需要在 startup.cs -> Configure 中配置認證中間件
// 自定義認證中間件
app.UseJwtTokenAuth(); //也可以app.UseMiddleware<JwtTokenAuth>();
4:開始測試
這個時候我們的自定義JWT授權認證已經結束了,我們開始測試,假設對某一個 api接口設置了權限:
image.png
在我們沒有輸入 Token 的時候,點擊測試接口會報錯:
a.gif
InvalidOperationException: No authenticationScheme was specified, and there was no DefaultChallengeScheme found. //沒有指定身份驗證方案, 也沒有發現默認挑戰方案。
這個錯誤很明顯,就是說我們沒有配置默認的認證方案,也沒有自定義身份驗證方案,
但是這個時候我們再進行試驗:
剛剛上邊的情況是我們沒有輸入 Token ,但是如果我們輸入token呢?看看是不是又會報錯?
a.gif
我們發現了什麼?!!沒有報錯,這是因爲什麼?欸,聰明的你應該想到了,請往下看,什麼是 聲明主體 ClaimsPrincipal 。
5、聲明主體 ClaimsPrincipal 是如何保存的?
在上邊,我們解決了一些問題,同時也出現了一個問題,就是爲什麼不輸入 Token 就報錯了,而輸入了 Bearer xxxxxxxxxxx 這樣的Token 就不報錯了呢?這裏要說到 聲明主體的作用了。
就是我們上邊寫的自定義中間件,大家可以再來看看:
// 自定義認證中間件,我們省略部分代碼,來分析分析
public Task Invoke(HttpContext httpContext)
{
//檢測是否包含'Authorization'請求頭
if (!httpContext.Request.Headers.ContainsKey("Authorization"))
{
//直接返回了 http 信道 ,就出現了我們上邊的報錯,沒有指定身份驗證方案, 也沒有發現默認挑戰方案。
return _next(httpContext);
}
//但是!請注意,這個時候我們輸入了 token,我們就會在 httpcontext 上下文中,添加上我們自己自定義的身份驗證方案!!!這就是沒有繼續報錯的根本原因
var tokenHeader = httpContext.Request.Headers["Authorization"].ToString().Replace("Bearer ", "");
//........
//授權
var claimList = new List<Claim>();
var claim = new Claim(ClaimTypes.Role, tm.Role);
claimList.Add(claim);
var identity = new ClaimsIdentity(claimList);
var principal = new ClaimsPrincipal(identity);
httpContext.User = principal;
}
return _next(httpContext);
}
這個時候你就應該明白了吧,
1、首先我們自定義授權認證,爲啥可以不用進行下邊截圖中官方認證那一塊的配置:
image.png
因爲這一塊官方的服務,就等同於我們的自定義身份驗證方案——中間件。
2、你應該明白,爲什麼不輸入token的時候報錯,而輸入了就不報錯了?
因爲沒有輸入的時候,直接 return了,並沒有在 httpContext 上下文中,進行配置聲明主體 httpContext.User = principal 。
所以說,我們無論是自定義中間件的自定義身份驗證方案,還是官方的認證方案,只要我們的登錄了,也就是說,只要我們實現了某種規則:在 Http 的 Header 裏,增加屬性Authorization ,並賦值 :Bearer xxxxxxxxxxxxxx;
這樣,就會觸發我們的內部服務,將當前 token 所攜帶的信息,進行自動解碼,然後填充到聲明主體裏(自定義中間件需要手動配置,官方的自動就實現該操作),
所以這個時候我們就可以輕鬆的拿到想到的東西,比如這裏這些:
6、無策略依然授權錯誤
上邊咱們說到了,如果我們自定義中間件的話,在中間件中,我們在 Claims 添加了角色的相關權限:
image.png
而且很自然的在 接口中,也是分爲兩種情況:要麼沒有加權限,要麼就是基於角色的加權:
image.png
但是如果這個時候,我們直接對接口增加 無任何策略 的加權:
image.png
就是沒有任何的策略,我們登錄,然後添加 token,一看,還是報錯了!具體的來看動圖:
a.gif
本來 [Authorize] 這種 無策略 的授權,按理說只需要我們登錄了就可以了,不需要其他任何限制就可以訪問,但是現在依然報錯401 ,證明我們的中間件並不能對這種方案起到效果,你可能會問,那帶有 Roles=“Admin” 的爲啥可以呢?反而這種無策略的不行呢,我個人感覺可能還是中間件咱們設計的解決方案就是基於角色授權的那種,(我也再研究研究,看看能不能完善下這個自定義中間件,使它能適應這個 無具體策略 的加權方案,但是可能寫到最後,就是無限接近官方的授權中間件了哈哈)。
這個時候我們發現,自定義中間件還是挺麻煩的,但是你通過自己使用自定義授權中間件,不僅僅可以瞭解到中間件的使用,還可以瞭解 netcore 到底是如何授權的機制,但是我還是建議大家使用官方的認證方案,畢竟他們考慮的很全面的。
那麼如果我們想要用官方的認證方案呢,要怎麼寫呢?請往下看:
二、JWT授權認證流程——官方認證
上邊咱們說完了自定義中間件的形式,發現了也方便的地方,也有不方便之處,雖然靈活的使用了自定義身份驗證,但是畢竟很受限,而且也無法對過期時間進行判斷,以後的文章你會看到《36 ║解決JWT自定義中間件授權過期問題》,這裏先不說,重點說說,如何通過官方認證來實現。
1:API接口授權策略
和上邊自定義的過程一模一樣,略。
2、官方默認認證配置
在剛剛上邊,咱們說到了一個錯誤,不知道還有沒有印象:
No authenticationScheme was specified, and there was no DefaultChallengeScheme found.
就是這個,自定義認證中間件呢,就是前者,那官方的,就是後者 DefaultChallengeScheme;
很簡單,只需要在 configureService 中,添加【統一認證】即可:
#region Authorize 授權認證
...
// 2.1 認證
#region 參數
// 讀取配置文件
var audienceConfig = Configuration.GetSection("Audience");
var symmetricKeyAsBase64 = audienceConfig["Secret"];
var keyByteArray = Encoding.ASCII.GetBytes(symmetricKeyAsBase64);
var signingKey = new SymmetricSecurityKey(keyByteArray);
var signingCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256);
#endregion
services.AddAuthentication(x =>
{
//看這個單詞熟悉麼?沒錯,就是上邊錯誤裏的那個。
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(c => {
c.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = signingKey,
ValidateIssuer = audienceConfig["Issuer"].ObjToBool(),//發行人
ValidateAudience = true,
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero,
RequireExpirationTime = true,
};
});
#endregion
具體的每個配置的含義呢,我的代碼裏都有,大家自己可以看看,都很簡單。
劃重點:我們就是用的這個官方默認的方案,來替換了我們自定義中間件的身份驗證方案,從而達到目的,說白了,就是官方封裝了一套方案,這樣我們就不用寫中間件了。
3、配置官方認證中間件
這個很簡單,還是在 configure 中添加:// 如果你想使用官方認證,必須在上邊ConfigureService 中,配置JWT的認證服務 (.AddAuthentication 和 .AddJwtBearer 二者缺一不可) app.UseAuthentication();
這樣就完成了,結果也不用看了,大家自行測試即可,無論添加或者不添加 token ,都不會報錯。
a.gif
4、補充:什麼是 Claim
如果對 claim[] 定義不是很理解,可以看看dudu大神的解釋《理解ASP.NET Core驗證模型(Claim, ClaimsIdentity, ClaimsPrincipal)不得不讀的英文博文》:
5、其他注意點
1、然後再Startup的Configure中,將TokenAuth註冊中間件
注意1:HTTP管道是有先後順序的,一定要寫在 app.Mvc() 之前,否則不起作用。
image.png
注意2:
這裏我們是自定義了認證中間件,來對JWT的字符串進行自定義授權認證,所以上邊都很正常,甚至我們的Token可以不用帶 Bearer 特定字符串,如果你以後遇到了使用官方認證中間件 UseAuthentication(),那麼就必須在 configureService 中對認證進行配置(而且Token傳遞的時候,也必須帶上"Bearer " 這樣的特定字符串,這也就是解釋了上文,爲啥要帶Bearer),這裏先打個預防針,因爲我的最新 Github 上已經使用了官方的認證中間件,所以除了上邊配置的那些服務外,還需要配置 Service.AddAuthentication 和 Service.AddJwtBearer 兩個服務。
// 如果你想使用官方認證,必須在上邊ConfigureService 中,配置JWT的認證服務
// .AddAuthentication 和 .AddJwtBearer 二者缺一不可
app.UseAuthentication();