一、項目需求
由於微服務技術的發展,需要對老的項目進行升級改造。其中一大難點就是老項目中使用了RBAC的權限系統,
面向微服務,首先要完成界面和認證后台的分離。
於是,對面向微服務的 RBAC 系統提出了如下需求:
A、認證系統需要遵守當前流行的 Auth2.0 協議,從而支持到單點登錄SSO;
B、客戶端使用純 js 開發,做到前后台分離;
C、實現 RBAC 權限控制。
針對需求,查了當前C#開源,對 RBAC 與 OpenId Connect 的結合比較有限。對是自己動手豐衣足食,提出了如下解決方案:
二、解決方案
1、總體解決方案及參考項目
針對需求的每一項提出解決方案
A、后台采用 OpenId Connect 包作 Auth2.0 協議的支持。github上源碼: openiddict-core;
B、客戶端使用 D2Admin + oidc-client.js 和后台配合完成 SSO 和 Auth2.0 的交互認證;
C、由於 Auth2.0 並不支持 RBAC 權限認證,自己開發了一個 Rbac.Auth.Introspection 工程,用來完成資源程序中的RBAC認證,
並且完全不破壞 Auth2.0 協議。本質上是對 Auth2.0 的擴展。github上的原始參考項目:AspNet.Security.OAuth.Extensions
開發好的庫可以直接用 Nuget 獲得:https://www.nuget.org/packages/Rbac.Auth.Introspection/
2、Auth2.0 與 RBAC 整合的基本原理
默認的認證中只要 Access Token 合法即通過,改為 Access Token 和 Url 同時合法才能通過即可。
Url 作為基礎的權限的參數,權限分配到角色,人員和再分配不同角色。
后台認證時,一個 Access Token 和 Url 進來,通過 Access Token 可查到人員,從而查到該人員是否有對應權限,如果有對應的 Url
則返回認證成功,否則失敗。
可以看出:是在標准的 Auth2.0 的 Acess Token 認證交互中擴展了 Url 參數參與認證,從而實現了 RBAC 的認證。
// Note: always specify the token_type_hint to help // the authorization server make a faster token lookup. var parameters = new Dictionary<string, string> { [RbacAuthIntrospectionConstants.Parameters.Token] = token, [RbacAuthIntrospectionConstants.Parameters.Path] = path, [RbacAuthIntrospectionConstants.Parameters.TokenTypeHint] = RbacAuthIntrospectionConstants.TokenTypeHints.AccessToken }; ... request.Content = new FormUrlEncodedContent(parameters); var notification = new SendIntrospectionRequestContext(Context, Scheme, Options, request, path, token); await Events.SendIntrospectionRequest(notification);
3、對於資源端權限定義的擴展
每個微服務都有自己提供服務的 Url 接口,這對於 RBAC 系統的配置提出了巨大的工作量,還需小心謹慎。有沒有自動方化的方法,
減少配置的工作量和出錯的可能性。為此在 Rbac.Auth.Introspection 中擴展了權限定義和權限列表生成接口。
3.1 資源端的初始化
// This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddAuthentication(options => { options.DefaultScheme = RbacAuthIntrospectionDefaults.AuthenticationScheme; }) .AddRbacAuthIntrospection(options => { options.Authority = new Uri("http://localhost:12345/"); options.Audiences.Add("resource-server-2"); options.ClientId = "resource-server-2"; options.ClientSecret = "C744604A-CD05-4092-9CF8-ECB7DC3499A2"; options.RequireHttpsMetadata = false; }); services.AddMvc(options => { options.EnableEndpointRouting = false; }); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { app.UseCors(builder => { builder.WithOrigins(new string[] { "http://localhost:12345", "http://localhost:8080" }); builder.WithMethods("GET"); builder.WithHeaders("Authorization"); }); app.UseStaticFiles(); app.UseAuthentication(); app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); }); }
通過指定 RbacAuthIntrospectionDefaults.AuthenticationScheme 來綁定 Rbac 的權限認證;
通過 AddRbacAuthIntrospection 設置服務器相關的參數;
通過 app.UseAuthentication 激活權限認證。
3.2 擴展了權限定義。
目的:方便自動化生成權限列表,並且和 Asp.Net Core 的 Identity 權限系統完美整合。
[AuthorizeMenu("資源示例", AuthenticationSchemes = RbacAuthIntrospectionDefaults.AuthenticationScheme)] public class ResourceController : Controller { [HttpGet] [PermissionFilter(PermissionType.Menu, "私有功能", Page = "/home/test")] public IActionResult Private() { var identity = User.Identity as ClaimsIdentity; if (identity == null) { return BadRequest(); } return Content($"You have authorized access to resources belonging to {identity.Name} on ResourceServer01."); } [HttpGet] [AllowAnonymous] public IActionResult Public() { return Content("This is a public endpoint that is at ResourceServer01; it does not require authorization."); } }
其中的 AuthorizeMenu 和 PermissionFilter 便是自定義的擴展,可以直接生成對應的權限。減少工作量和出錯機會。
每次修正代碼后也方便自動修正后台服務器中對應的權限,保持系統與代碼的一至性。
3.3 如何自動化生成權限列表
對於資源端,通過 ApplicationController 的 GetPermissionList 接口來獲得權限列表。(appId 是用於標識本資源的唯一Id)
public class ApplicationController : Controller { [HttpGet] public IActionResult GetPermissionList(string appId) { var resList = new List<PermissionDTO>(); var controllerList = GetTypes().Where(t => t.IsSubclassOf(typeof(Controller))).ToList(); if (controllerList.Count > 0) { resList = ControllerTools.CreatePermissionDTOList(appId, controllerList); } var res = new JsonResult(new { code = 0, msg = "", data = JsonConvert.SerializeObject(resList) }); return res; } private IEnumerable<Type> GetTypes() { foreach (var asm in AppDomain.CurrentDomain.GetAssemblies()) foreach (var t in GetTypes(asm)) yield return t; } private IEnumerable<Type> GetTypes(System.Reflection.Assembly asm) { Type[] ts; try { ts = asm.GetTypes(); } catch { yield break; } foreach (var t in ts) yield return t; } }
三、參考
OIDC(OpenId Connect)身份認證(核心部分)
HANDLING ACCESS TOKENS FOR PRIVATE APIS IN ASP.NET CORE
RBAC | 使用authing實現基於角色的訪問控制