用C#实现基于(OpenId Connect)的单点登录与RBAC权限验证(SSO,OIDC,RBAC)

一、项目需求

       由于微服务技术的发展,需要对老的项目进行升级改造。其中一大难点就是老项目中使用了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)的SSO

 OIDC(OpenId Connect)身份认证(核心部分)

 HANDLING ACCESS TOKENS FOR PRIVATE APIS IN ASP.NET CORE

 RBAC | 使用authing实现基于角色的访问控制

  

原文地址:https://www.cnblogs.com/citycomputing/p/13221295.html