【.NET Core專案實戰-統一認證平臺】第七章 閘道器篇-自定義客戶端限流
【.NET Core專案實戰-統一認證平臺】開篇及目錄索引
上篇文章我介紹瞭如何在閘道器上增加自定義客戶端授權功能,從設計到編碼實現,一步一步詳細講解,相信大家也掌握了自定義中介軟體的開發技巧了,本篇我們將介紹如何實現自定義客戶端的限流功能,來進一步完善閘道器的基礎功能。
.netcore專案實戰交流群(637326624),有興趣的朋友可以在群裡交流討論。
一、功能描述
限流就是為了保證閘道器在高併發或瞬時併發時,在服務能承受範圍內,犧牲部分請求為代價,保證系統的整體可用性而做的安全策略,避免單個服務影響整體閘道器的服務能力。
比如閘道器有商品查詢介面 ,能接受的極限請求是每秒100次查詢,如果此時不限流,可能因為瞬時請求太大,造成服務卡死或崩潰的情況,這種情況可以使用Ocelot
Ocelot
配置限流來進行自定義控制了,這塊就需要我們增加自定義限流管道來實現功能。
下面我們就該功能如何實現展開講解,希望大家先理解下功能需求,然後在延伸到具體實現。
二、資料庫設計
限流這塊設計表結構和關係如下。
主要有限流規則表、路由限流規則表、限流組表、限流組策略表、客戶端授許可權流組表、客戶端白名單表組成,設計思想就是客戶端請求時先檢查是否在白名單,如果白名單不存在,就檢查是否在限流組裡,如果在限流組裡校驗限流的規則是什麼,然後比對這個規則和當前請求次數看是否能夠繼續訪問,如果超過限流策略直接返回429狀態,否則路由到下端請求。
梳理下後發現流程不是很複雜,最起碼實現的思路非常清晰,然後我們就運用上篇自定義授權中介軟體的方式來開發我們第二個中介軟體,自定義限流中介軟體。
三、功能實現
1、功能開啟配置
閘道器應該支援自定義客戶端限流中介軟體是否啟用,因為一些小型專案是不需要對每個客戶端進行單獨限流的,中型和大型專案才有可能遇到自定義配置情況,所以我們需要在配置檔案增加配置選項。在AhphOcelotConfiguration.cs
配置類中增加屬性,預設不開啟。
/// <summary> /// 金焰的世界 /// 2018-11-18 /// 是否開啟自定義限流,預設不開啟 /// </summary> public bool ClientRateLimit { get; set; } = false; /// <summary> /// 金焰的世界 /// 2018-11-18 /// 客戶端限流快取時間,預設30分鐘 /// </summary> public int ClientRateLimitCacheTime { get; set; } = 1800;
那我們如何把自定義的限流增加到閘道器流程裡呢?這塊我們就需要訂製自己的限流中介軟體。
2、實現客戶端限流中介軟體
首先我們定義一個自定義限流中介軟體AhphClientRateLimitMiddleware
,需要繼承OcelotMiddleware
,然後我們要實現Invoke
方法,詳細程式碼如下。
using Ctr.AhphOcelot.Configuration;
using Ctr.AhphOcelot.Errors;
using Ocelot.Logging;
using Ocelot.Middleware;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Ctr.AhphOcelot.RateLimit.Middleware
{
/// <summary>
/// 金焰的世界
/// 2018-11-18
/// 自定義客戶端限流中介軟體
/// </summary>
public class AhphClientRateLimitMiddleware : OcelotMiddleware
{
private readonly IClientRateLimitProcessor _clientRateLimitProcessor;
private readonly OcelotRequestDelegate _next;
private readonly AhphOcelotConfiguration _options;
public AhphClientRateLimitMiddleware(OcelotRequestDelegate next,
IOcelotLoggerFactory loggerFactory,
IClientRateLimitProcessor clientRateLimitProcessor,
AhphOcelotConfiguration options)
: base(loggerFactory.CreateLogger<AhphClientRateLimitMiddleware>())
{
_next = next;
_clientRateLimitProcessor = clientRateLimitProcessor;
_options = options;
}
public async Task Invoke(DownstreamContext context)
{
var clientId = "client_cjy"; //使用預設的客戶端
if (!context.IsError)
{
if (!_options.ClientRateLimit)
{
Logger.LogInformation($"未啟用客戶端限流中介軟體");
await _next.Invoke(context);
}
else
{
//非認證的渠道
if (!context.DownstreamReRoute.IsAuthenticated)
{
if (context.HttpContext.Request.Headers.Keys.Contains(_options.ClientKey))
{
clientId = context.HttpContext.Request.Headers[_options.ClientKey].First();
}
}
else
{//認證過的渠道,從Claim中提取
var clientClaim = context.HttpContext.User.Claims.FirstOrDefault(p => p.Type == _options.ClientKey);
if (!string.IsNullOrEmpty(clientClaim?.Value))
{
clientId = clientClaim?.Value;
}
}
//路由地址
var path = context.DownstreamReRoute.UpstreamPathTemplate.OriginalValue;
//1、校驗路由是否有限流策略
//2、校驗客戶端是否被限流了
//3、校驗客戶端是否啟動白名單
//4、校驗是否觸發限流及計數
if (await _clientRateLimitProcessor.CheckClientRateLimitResultAsync(clientId, path))
{
await _next.Invoke(context);
}
else
{
var error = new RateLimitOptionsError($"請求路由 {context.HttpContext.Request.Path}觸發限流策略");
Logger.LogWarning($"路由地址 {context.HttpContext.Request.Path} 觸發限流策略. {error}");
SetPipelineError(context, error);
}
}
}
else
{
await _next.Invoke(context);
}
}
}
}
首先我們來分析下我們的程式碼,為了知道是哪個客戶端請求了我們閘道器,需要提取clientId
,分別從無需授權介面和需要授權介面兩個方式提取,如果提取不到值直接給定預設值,放到全侷限流裡,防止繞過限流策略。然後根據客戶端通過4步檢驗下是否允許訪問(後面會介紹這4步怎麼實現),如果滿足限流策略直接返回限流錯誤提醒。
有了這個中介軟體,那麼如何新增到Ocelot的管道里呢?上一篇介紹的非常詳細,這篇我就不介紹了,自定義限流中介軟體擴充套件AhphClientRateLimitMiddlewareExtensions
,程式碼如下。
using Ocelot.Middleware.Pipeline;
using System;
using System.Collections.Generic;
using System.Text;
namespace Ctr.AhphOcelot.RateLimit.Middleware
{
/// <summary>
/// 金焰的世界
/// 2018-11-18
/// 限流中介軟體擴充套件
/// </summary>
public static class AhphClientRateLimitMiddlewareExtensions
{
public static IOcelotPipelineBuilder UseAhphAuthenticationMiddleware(this IOcelotPipelineBuilder builder)
{
return builder.UseMiddleware<AhphClientRateLimitMiddleware>();
}
}
}
有了這個中介軟體擴充套件後,我們就在管道的合適地方加入我們自定義的中介軟體。我們新增我們自定義的管道擴充套件OcelotPipelineExtensions
,然後把自定義限流中介軟體加入到認證之後。
//新增自定義限流中介軟體 2018-11-18 金焰的世界
builder.UseAhphClientRateLimitMiddleware();
現在我們完成了閘道器的擴充套件和應用,是時候把定義的IClientRateLimitProcessor
介面實現了 ,是不是感覺做一箇中間件很簡單呢?而且每一步都是層層關聯,只要一步一步按照自己的想法往下寫就能實現。
3、結合資料庫實現校驗及快取
首先我們新建AhphClientRateLimitProcessor
類來實現介面,中間增加必要的快取和業務邏輯,詳細程式碼如下。
using Ctr.AhphOcelot.Configuration;
using Ocelot.Cache;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace Ctr.AhphOcelot.RateLimit
{
/// <summary>
/// 金焰的世界
/// 2018-11-19
/// 實現客戶端限流處理器
/// </summary>
public class AhphClientRateLimitProcessor : IClientRateLimitProcessor
{
private readonly AhphOcelotConfiguration _options;
private readonly IOcelotCache<ClientRoleModel> _ocelotCache;
private readonly IOcelotCache<RateLimitRuleModel> _rateLimitRuleCache;
private readonly IOcelotCache<AhphClientRateLimitCounter?> _clientRateLimitCounter;
private readonly IClientRateLimitRepository _clientRateLimitRepository;
private static readonly object _processLocker = new object();
public AhphClientRateLimitProcessor(AhphOcelotConfiguration options,IClientRateLimitRepository clientRateLimitRepository, IOcelotCache<AhphClientRateLimitCounter?> clientRateLimitCounter, IOcelotCache<ClientRoleModel> ocelotCache, IOcelotCache<RateLimitRuleModel> rateLimitRuleCache)
{
_options = options;
_clientRateLimitRepository = clientRateLimitRepository;
_clientRateLimitCounter = clientRateLimitCounter;
_ocelotCache = ocelotCache;
_rateLimitRuleCache = rateLimitRuleCache;
}
/// <summary>
/// 校驗客戶端限流結果
/// </summary>
/// <param name="clientid">客戶端ID</param>
/// <param name="path">請求地址</param>
/// <returns></returns>
public async Task<bool> CheckClientRateLimitResultAsync(string clientid, string path)
{
var result = false;
var clientRule = new List<AhphClientRateLimitOptions>();
//1、校驗路由是否有限流策略
result = !await CheckReRouteRuleAsync(path);
if (!result)
{//2、校驗客戶端是否被限流了
var limitResult = await CheckClientRateLimitAsync(clientid, path);
result = !limitResult.RateLimit;
clientRule = limitResult.rateLimitOptions;
}
if (!result)
{//3、校驗客戶端是否啟動白名單
result = await CheckClientReRouteWhiteListAsync(clientid, path);
}
if (!result)
{//4、校驗是否觸發限流及計數
result = CheckRateLimitResult(clientRule);
}
return result;
}
/// <summary>
/// 檢驗是否啟用限流規則
/// </summary>
/// <param name="path">請求地址</param>
/// <returns></returns>
private async Task<bool> CheckReRouteRuleAsync(string path)
{
var region = _options.RedisKeyPrefix + "CheckReRouteRuleAsync";
var key = region + path;
var cacheResult = _ocelotCache.Get(key, region);
if (cacheResult != null)
{//提取快取資料
return cacheResult.Role;
}
else
{//重新獲取限流策略
var result = await _clientRateLimitRepository.CheckReRouteRuleAsync(path);
_ocelotCache.Add(key, new ClientRoleModel() { CacheTime = DateTime.Now, Role = result }, TimeSpan.FromSeconds(_options.ClientRateLimitCacheTime), region);
return result;
}
}
/// <summary>
/// 校驗客戶端限流規則
/// </summary>
/// <param name="clientid">客戶端ID</param>
/// <param name="path">請求地址</param>
/// <returns></returns>
private async Task<(bool RateLimit, List<AhphClientRateLimitOptions> rateLimitOptions)> CheckClientRateLimitAsync(string clientid, string path)
{
var region = _options.RedisKeyPrefix + "CheckClientRateLimitAsync";
var key = region + clientid + path;
var cacheResult = _rateLimitRuleCache.Get(key, region);
if (cacheResult != null)
{//提取快取資料
return (cacheResult.RateLimit, cacheResult.rateLimitOptions);
}
else
{//重新獲取限流策略
var result = await _clientRateLimitRepository.CheckClientRateLimitAsync(clientid, path);
_rateLimitRuleCache.Add(key, new RateLimitRuleModel() { RateLimit=result.RateLimit, rateLimitOptions=result.rateLimitOptions }, TimeSpan.FromSeconds(_options.ClientRateLimitCacheTime), region);
return result;
}
}
/// <summary>
/// 校驗是否設定了路由白名單
/// </summary>
/// <param name="clientid">客戶端ID</param>
/// <param name="path">請求地址</param>
/// <returns></returns>
private async Task<bool> CheckClientReRouteWhiteListAsync(string clientid, string path)
{
var region = _options.RedisKeyPrefix + "CheckClientReRouteWhiteListAsync";
var key = region +clientid+ path;
var cacheResult = _ocelotCache.Get(key, region);
if (cacheResult != null)
{//提取快取資料
return cacheResult.Role;
}
else
{//重新獲取限流策略
var result = await _clientRateLimitRepository.CheckClientReRouteWhiteListAsync(clientid,path);
_ocelotCache.Add(key, new ClientRoleModel() { CacheTime = DateTime.Now, Role = result }, TimeSpan.FromSeconds(_options.ClientRateLimitCacheTime), region);
return result;
}
}
/// <summary>
/// 校驗完整的限流規則
/// </summary>
/// <param name="rateLimitOptions">限流配置</param>
/// <returns></returns>
private bool CheckRateLimitResult(List<AhphClientRateLimitOptions> rateLimitOptions)
{
bool result = true;
if (rateLimitOptions != null && rateLimitOptions.Count > 0)
{//校驗策略
foreach (var op in rateLimitOptions)
{
AhphClientRateLimitCounter counter = new AhphClientRateLimitCounter(DateTime.UtcNow, 1);
//分別對每個策略校驗
var enablePrefix = _options.RedisKeyPrefix + "RateLimitRule";
var key = AhphOcelotHelper.ComputeCounterKey(enablePrefix, op.ClientId, op.Period, op.RateLimitPath);
var periodTimestamp = AhphOcelotHelper.ConvertToSecond(op.Period);
lock (_processLocker)
{
var rateLimitCounter = _clientRateLimitCounter.Get(key, enablePrefix);
if (rateLimitCounter.HasValue)
{//提取當前的計數情況
// 請求次數增長
var totalRequests = rateLimitCounter.Value.TotalRequests + 1;
// 深拷貝
counter = new AhphClientRateLimitCounter(rateLimitCounter.Value.Timestamp, totalRequests);
}
else
{//寫入限流策略
_clientRateLimitCounter.Add(key, counter,TimeSpan.FromSeconds(periodTimestamp), enablePrefix);
}
}
if (counter.TotalRequests > op.Limit)
{//更新請求記錄,並標記為失敗
result = false;
}
if (counter.TotalRequests > 1 && counter.TotalRequests <= op.Limit)
{//更新快取配置資訊
//獲取限流剩餘時間
var cur = (int)(counter.Timestamp.AddSeconds(periodTimestamp) - DateTime.UtcNow).TotalSeconds;
_clientRateLimitCounter.Add(key, counter, TimeSpan.FromSeconds(cur), enablePrefix);
}
}
}
return result;
}
}
}
我們來分析下這塊程式碼,裡面涉及了限流的提取和實現規則,首先我們注入了資料庫實體介面和快取資訊,實現步驟是參照之前的流程。
主要流程如下:
1、路由是否啟用限流,如果未啟用直接完成校驗,如果進行第2步判斷.
2、客戶端對應的路由是否設定了限流規則,如果未設定,直接完成校驗,否則進入第3步判斷.
3、客戶端是否開啟了路由白名單功能,如果開啟了直接完成校驗,否則進入第4步。
4、使用Redis來進行限流的判斷。使用的就是計數器方法,結合redis設定key的過期時間來實現的。
為了減少後端請求,在資料庫提取的方法前都加入了快取,現在我們需要把用到的介面新增到入口進行注入。
builder.Services.AddSingleton<IOcelotCache<RateLimitRuleModel>, InRedisCache<RateLimitRuleModel>>();
builder.Services.AddSingleton<IOcelotCache<AhphClientRateLimitCounter?>, InRedisCache<AhphClientRateLimitCounter?>>();
現在我們還剩下IClientRateLimitRepository
介面未實現,現在只要實現這個介面,然後注入下,我們就完成了限流中介軟體的開發了,我們根據限流的流程,梳理了實現,現在有3個方法需要進行實現。
新建SqlServerClientRateLimitRepository
類,來開始實現我們與資料庫的操作,有了上面的分析思路,現在就是把一個一個詳細確定的方法實現而已,太簡單了,只要花了幾分鐘後,就可以瞬間寫出如下程式碼。
using Ctr.AhphOcelot.Configuration;
using Ctr.AhphOcelot.RateLimit;
using Dapper;
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Text;
using System.Threading.Tasks;
namespace Ctr.AhphOcelot.DataBase.SqlServer
{
/// <summary>
/// 金焰的世界
/// 2018-11-19
/// 客戶端限流資訊提取
/// </summary>
public class SqlServerClientRateLimitRepository : IClientRateLimitRepository
{
private readonly AhphOcelotConfiguration _option;
public SqlServerClientRateLimitRepository(AhphOcelotConfiguration option)
{
_option = option;
}
/// <summary>
/// 校驗客戶端限流規則
/// </summary>
/// <param name="clientid">客戶端ID</param>
/// <param name="path">請求地址</param>
/// <returns></returns>
public async Task<(bool RateLimit, List<AhphClientRateLimitOptions> rateLimitOptions)> CheckClientRateLimitAsync(string clientid, string path)
{
using (var connection = new SqlConnection(_option.DbConnectionStrings))
{
string sql = @"SELECT DISTINCT UpstreamPathTemplate AS RateLimitPath,LimitPeriod AS Period,LimitNum AS Limit,ClientId FROM AhphReRoute T1 INNER JOIN AhphReRouteLimitRule T2 ON T1.ReRouteId=T2.ReRouteId
INNER JOIN AhphLimitRule T3 ON T2.RuleId=T3.RuleId INNER JOIN AhphLimitGroupRule T4 ON
T2.ReRouteLimitId=T4.ReRouteLimitId INNER JOIN AhphLimitGroup T5 ON T4.LimitGroupId=T5.LimitGroupId
INNER JOIN AhphClientLimitGroup T6 ON T5.LimitGroupId=T6.LimitGroupId INNER JOIN
AhphClients T7 ON T6.Id=T7.Id
WHERE T1.InfoStatus=1 AND [email protected] AND T3.InfoStatus=1 AND T5.InfoStatus=1
AND [email protected] AND Enabled=1";
var result = (await connection.QueryAsync<AhphClientRateLimitOptions>(sql, new { clientid, path }))?.AsList();
if (result != null && result.Count > 0)
{
return (true, result);
}
else
{
return (false, null);
}
}
}
/// <summary>
/// 校驗是否設定了路由白名單
/// </summary>
/// <param name="clientid">客戶端ID</param>
/// <param name="path">請求地址</param>
/// <returns></returns>
public async Task<bool> CheckClientReRouteWhiteListAsync(string clientid, string path)
{
using (var connection = new SqlConnection(_option.DbConnectionStrings))
{
string sql = @"SELECT COUNT(1) FROM AhphReRoute T1 INNER JOIN AhphClientReRouteWhiteList T2 ON T1.ReRouteId=T2.ReRouteId
INNER JOIN AhphClients T3 ON T2.Id=T3.Id WHERE T1.InfoStatus=1 AND [email protected] AND
[email protected] AND Enabled=1";
var result = await connection.QueryFirstOrDefaultAsync<int>(sql, new { clientid,path });
return result > 0;
}
}
/// <summary>
/// 校驗是否啟用限流規則
/// </summary>
/// <param name="path">請求地址</param>
/// <returns></returns>
public async Task<bool> CheckReRouteRuleAsync(string path)
{
using (var connection = new SqlConnection(_option.DbConnectionStrings))
{
string sql = @"SELECT COUNT(1) FROM AhphReRoute T1 INNER JOIN AhphReRouteLimitRule T2 ON T1.ReRouteId=T2.ReRouteId
INNER JOIN AhphLimitRule T3 ON T2.RuleId=T3.RuleId WHERE T1.InfoStatus=1 AND [email protected]
AND T3.InfoStatus=1";
var result = await connection.QueryFirstOrDefaultAsync<int>(sql, new { path });
return result > 0;
}
}
}
}
主要就是注意下表之間的關係,把實現注入到AddAhphOcelot
裡,現在就可以測試開始自定義客戶端限流中介軟體。
builder.Services.AddSingleton<IClientRateLimitRepository, SqlServerClientRateLimitRepository>();
4、測試限流中介軟體
為了把把所有情況都測試一遍,先從開啟限流,什麼都不寫入看是否能夠正常執行。
option.ClientRateLimit = true;
還記得我們上篇的兩個客戶端和能訪問的頁面嗎?就用它們來測試,結果顯示正常,說明不開啟限流沒有影響。
開啟/cjy/values
2個限流規則,一個每1分鐘訪問1次,一個每1分鐘訪問60次。
--1、插入限流規則
INSERT INTO AhphLimitRule VALUES('每1分鐘訪問1次','1m',1,1);
INSERT INTO AhphLimitRule VALUES('每1分鐘訪問60次','1m',60,1);
--2、應用到/cjy/values路由
INSERT INTO AhphReRouteLimitRule VALUES(1,1);
INSERT INTO AhphReRouteLimitRule VALUES(2,1);
因為還未給客戶端應用規則,所以應該也是可以正常訪問,可以使用PostMan
測試下,測試時需要注意下快取,因為所有的訪問都啟用的預設快取策略,經測試得到預期效果。
現在開始把限流分別應用到客戶端1和客戶端2,看下限流效果。
--3、插入測試分組
INSERT INTO AhphLimitGroup VALUES('限流分組1','',1);
INSERT INTO AhphLimitGroup VALUES('限流分組2','',1);
--4、分組應用策略
INSERT INTO AhphLimitGroupRule VALUES(1,1);
INSERT INTO AhphLimitGroupRule VALUES(2,2);
--5、客戶端應用限流分組
INSERT INTO AhphClientLimitGroup VALUES(2,1);
INSERT INTO AhphClientLimitGroup VALUES(3,2);
然後使用PostMan
測試客戶端1和客戶端2,結果如下,超過設定的頻率後不返回結果,達到預期目的,但是返回的是404錯誤,強迫症患者表示這不優雅啊,應該是429 Too Many Requests
,那我們如何修改呢?
這裡就需要了解下錯誤資訊是如何輸出的,需要檢視Ocelot
原始碼,您會發現IErrorsToHttpStatusCodeMapper
介面和ErrorsToHttpStatusCodeMapper
實現,程式碼如下,
using System.Collections.Generic;
using System.Linq;
using Ocelot.Errors;
namespace Ocelot.Responder
{
public class ErrorsToHttpStatusCodeMapper : IErrorsToHttpStatusCodeMapper
{
public int Map(List<Error> errors)
{
if (errors.Any(e => e.Code == OcelotErrorCode.UnauthenticatedError))
{
return 401;
}
if (errors.Any(e => e.Code == OcelotErrorCode.UnauthorizedError
|| e.Code == OcelotErrorCode.ClaimValueNotAuthorisedError
|| e.Code == OcelotErrorCode.ScopeNotAuthorisedError
|| e.Code == OcelotErrorCode.UserDoesNotHaveClaimError
|| e.Code == OcelotErrorCode.CannotFindClaimError))
{
return 403;
}
if (errors.Any(e => e.Code == OcelotErrorCode.RequestTimedOutError))
{
return 503;
}
if (errors.Any(e => e.Code == OcelotErrorCode.UnableToFindDownstreamRouteError))
{
return 404;
}
if (errors.Any(e => e.Code == OcelotErrorCode.UnableToCompleteRequestError))
{
return 500;
}
return 404;
}
}
}
可以發現因為未定義RateLimitOptionsError
錯誤的狀態碼,增加一個判斷即可,那我們重寫下把,然後整合在我們自己的中介軟體裡,這塊在後期有很多擴充套件能夠用到,增加如下程式碼。
if (errors.Any(e => e.Code == OcelotErrorCode.RateLimitOptionsError))
{
return 429;
}
然後重新注入下。
builder.Services.AddSingleton<IErrorsToHttpStatusCodeMapper, AhphErrorsToHttpStatusCodeMapper>();
在重新測試下訪問限流地址。
奈斯,達到了我們預期的效果,.netcore
開發魅力體現出來了嗎?
我們增加客戶端1的路由白名單,然後再繼續測試看是否解除限流限制?
--6、設定客戶端1/cjy/values路由白名單
INSERT INTO AhphClientReRouteWhiteList VALUES(1,2);
注意測試時清除快取
經測試不受限流控制,達到了我們最終目的,到此限流功能全部實現。
5、增加mysql支援
直接重寫IClientRateLimitRepository
實現,然後注入實現。
builder.Services.AddSingleton<IClientRateLimitRepository, MySqlClientRateLimitRepository>();
四、總結及預告
本篇我們講解的是閘道器如何實現自定義客戶端限流功能,從設計到實現一步一步詳細講解,雖然只用一篇就寫完了,但是涉及的知識點還是非常多的,希望大家認真理解實現的思想,看我是如何從規劃到實現的,為了更好的幫助大家理解。大家可以根據部落格內容自己手動實現下,有利於消化,如果在操作中遇到什麼問題,可以加.NET Core專案實戰交流群(QQ群號:637326624)
諮詢作者。
從下一篇開始介紹IdentityServer4
的相關應用,並配合我們的閘道器實現認證,在跟我教程學習的朋友,可以自己先預習下。