ocelot 自定義認證和授權 Intro 最近又重新啟動了網關項目,服務越來越多,每個服務都有一個地址,這無論是對於前端還是後端開發調試都是比較麻煩的,前端需要定義很多 baseUrl,而後端需要沒有代碼調試的時候需要對每個服務的地址都收藏著或者記在哪裡,用的時候要先找到地址,甚是麻煩,有了網關之 ...
ocelot 自定義認證和授權
Intro
最近又重新啟動了網關項目,服務越來越多,每個服務都有一個地址,這無論是對於前端還是後端開發調試都是比較麻煩的,前端需要定義很多 baseUrl,而後端需要沒有代碼調試的時候需要對每個服務的地址都收藏著或者記在哪裡,用的時候要先找到地址,甚是麻煩,有了網關之後,所有的 API 就有了統一的入口,對於前端來說就不需要維護那麼多的 baseUrl,只需要網關的地址即可,對於後端來說也是同樣的。
Ocelot 簡介
Ocelot是一個用.NET Core實現並且開源的API網關,它功能強大,包括了:路由、請求聚合、服務發現、認證、鑒權、限流熔斷等功能,這些功能只都只需要簡單的配置即可完成。
自定義認證授權
自定義認證授權思想,這裡的示例是一個基於用戶角色授權的示例:
- 基於 url 以及 請求 Method 查詢需要的許可權
- 如果不需要用戶登錄就可以訪問,就直接往下游服務轉發
- 如果需要許可權,判斷當前登錄用戶的角色是否可以以當前 Method 訪問當前路徑
- 如果可以訪問就轉發到下游服務,如果沒有許可權訪問根據用戶是否登錄,已登錄返回 403 Forbidden,未登錄返回 401 Unauthorized
Ocelot 的 認證授權不能滿足我的需要,於是就自己擴展了一個 Ocelot 的中間件
示例代碼
public class ApiPermission
{
public string AllowedRoles { get; set; }
public string PathPattern { get; set; }
public string Method { get; set; }
}
public class UrlBasedAuthenticationMiddleware : Ocelot.Middleware.OcelotMiddleware
{
private readonly IConfiguration _configuration;
private readonly IMemoryCache _memoryCache;
private readonly OcelotRequestDelegate _next;
public UrlBasedAuthenticationMiddleware(OcelotRequestDelegate next, IConfiguration configuration, IMemoryCache memoryCache, IOcelotLoggerFactory loggerFactory) : base(loggerFactory.CreateLogger<UrlBasedAuthenticationMiddleware>())
{
_next = next;
_configuration = configuration;
_memoryCache = memoryCache;
}
public async Task Invoke(DownstreamContext context)
{
var permissions = await _memoryCache.GetOrCreateAsync("ApiPermissions", async entry =>
{
using (var conn = new SqlConnection(_configuration.GetConnectionString("ApiPermissions")))
{
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1);
return (await conn.QueryAsync<ApiPermission>("SELECT * FROM dbo.ApiPermissions")).ToArray();
}
});
var result = await context.HttpContext.AuthenticateAsync(context.DownstreamReRoute.AuthenticationOptions.AuthenticationProviderKey);
context.HttpContext.User = result.Principal;
var user = context.HttpContext.User;
var request = context.HttpContext.Request;
var permission = permissions.FirstOrDefault(p =>
request.Path.Value.Equals(p.PathPattern, StringComparison.OrdinalIgnoreCase) && p.Method.ToUpper() == request.Method.ToUpper());
if (permission == null)// 完全匹配不到,再根據正則匹配
{
permission =
permissions.FirstOrDefault(p =>
Regex.IsMatch(request.Path.Value, p.PathPattern, RegexOptions.IgnoreCase) && p.Method.ToUpper() == request.Method.ToUpper());
}
if (!user.Identity.IsAuthenticated)
{
if (permission != null && string.IsNullOrWhiteSpace(permission.AllowedRoles)) //預設需要登錄才能訪問
{
//context.HttpContext.User = new ClaimsPrincipal(new ClaimsIdentity(new[] { new Claim(ClaimTypes.Name, "Anonymous") }, context.DownstreamReRoute.AuthenticationOptions.AuthenticationProviderKey));
}
else
{
SetPipelineError(context, new UnauthenticatedError("unauthorized, need login"));
return;
}
}
else
{
if (!string.IsNullOrWhiteSpace(permission?.AllowedRoles) &&
!permission.AllowedRoles.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Any(r => user.IsInRole(r)))
{
SetPipelineError(context, new UnauthorisedError("forbidden, have no permission"));
return;
}
}
await _next.Invoke(context);
}
}
認證授權之後
經過上面的認證授權之後,就可以往下游轉發請求了,下游的服務有的可能會需要判斷用戶的角色或者需要根據用戶的 userId 或者 Name 或者 郵箱去檢查某些數據的許可權,這裡就需要把在網關完成認證之後,得到的用戶信息傳遞給下游服務,這裡我選擇的是通過請求頭把用戶信息從網關服務傳遞到下游服務, Ocelot 可以把 Claims 中的信息轉換到 Header ,詳細參考Ocelot文檔,但是實現有個bug,如果有多個值他只會取第一個,詳見issue,可以自己擴展一個 ocelot 的中間件替換掉原有的中間件。
傳遞到下游服務之後,下游服務在需要用戶信息的地方就可以從請求頭中獲取用戶信息,如果下游服務比較複雜,不方便改動的話可以實現一個自定義的請求頭認證,可以參考我的這一篇文章
Memo
如果有什麼問題或建議,歡迎提出一起交流