zoukankan      html  css  js  c++  java
  • ASP.NET Core

      前言  

      在正常的情况下,当我们系统用到JWT认证方式时,需要在Http请求头添加Authorization: XXX,这样在后台服务的控制器中打上[Authorize]授权标签,就限定所有的请求必须通过鉴权方可访问。

      在【ASP.NET Core - 基于IHttpContextAccessor实现系统级别身份标识】这篇文章中我们能够注意到,通过IHttpContextAccessor获取基于请求生成的HttpContext后,我们是能够拿到基于该次请求的所有http信息的。这就给予了我们可以基于Http的请求做自定义请求头的策略来满足我们业务的扩展。

      场景

       在不少的场景中,特别是多租户的场景中,我们是需要知道当前是哪个租户下的用户在操作,而我们又不需要把这租户作为业务参数传递,毕竟如果按照业务参数这样的方式去实现的话,就好比从源头传入了一个污染源,需要一路污染下去,这是非常不推荐的方式,这时就可以利用自定义http请求头来传递这个参数,在最终需要用到这个参数的时候获取出来。

      在我们的系统层面,例如我们的应用部署在K8S集群中的网络,在网关层解析了JWT通过认证后放行之后,内网的应用服务可以依赖内网的保护把JWT的认证删减掉(这样在一定程上是可以提高性能的),我们就可以通过以下自定义头的实现方式,把租户信息携带在http头部,一路传递下去。

       实现

      类似于我们在【ASP.NET Core - 基于IHttpContextAccessor实现系统级别身份标识】中的IHttpContextAccessor实现方式一样,我们先定义我们的IHttpHeaderAccessor用来获取HttpHeader

        /// <summary>
        /// httpcontext的header
        /// </summary>
        public interface IHttpHeaderAccessor
        {
            /// <summary>
            /// the httpheader
            /// </summary>
            IHeaderDictionary HttpHeader { get; }
        }

       HttpHeaderAccessor 的实现  

        /// <summary>
        /// httpheader访问器
        /// </summary>
        public class HttpHeaderAccessor : IHttpHeaderAccessor
        {
            /// <summary>
            /// the HttpContextAccessor
            /// </summary>
            private readonly IHttpContextAccessor _httpContextAccessor;
    
            public IHeaderDictionary HttpHeader => _httpContextAccessor.HttpContext.Request.Headers;
    
            /// <summary>
            /// ctor
            /// </summary>
            /// <param name="httpContextAccessor">the HttpContextAccessor</param>
            public HttpHeaderAccessor(IHttpContextAccessor httpContextAccessor)
            {
                _httpContextAccessor = httpContextAccessor;
            }
        }

       定义http请求头的自定义参数

        public interface ICustomHeaderAccessor
        {        
            /// <summary>
            /// 租户Id
            /// </summary>
            string TenantId { get; }
        }

       http请求头的自定义参数的获取,这里是直接从头部获取到租户信息

      /// <summary>
        /// 获取自定义http头
        /// </summary>
        public class CustomHeaderAccessor : ICustomHeaderAccessor
        {
            protected IHttpHeaderAccessor _httpHeaderAccessor { get; }
    
            /// <summary>
            /// ctor
            /// </summary>
            /// <param name="httpHeaderAccessor"></param>
            public CustomHeaderAccessor(IHttpHeaderAccessor httpHeaderAccessor)
            {
                _httpHeaderAccessor = httpHeaderAccessor;
            }
    
    
            /// <summary>
            /// 租户Id
            /// </summary>
            public string TenantId
            {
                get
                {
                    return _httpHeaderAccessor.HttpHeader[HeaderConst.TenantId];
                }
            }
        }

       扩展

      在【ASP.NET Core - 基于IHttpContextAccessor实现系统级别身份标识】我们知道IHttpContextAccessor,IHttpHeaderAccessor,ICustomHeaderAccessor都需要注册,有了上面的这些方法定义,我们定义一个ServiceCollection的扩展方法进行注册

        /// <summary>
        /// 基于IServiceCollection的扩展类
        /// </summary>
        public static class ServiceCollectionExtension
        {
            /// <summary>
            /// 注册IHttpContextAccessor,IHttpHeaderAccessor和ICustomHeaderAccessor
            /// </summary>
            /// <param name="services">the IServiceCollection</param>
            /// <returns></returns>
            public static IServiceCollection AddCustomHttpHeader(this IServiceCollection services)
            {
                services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
                services.AddSingleton<IHttpHeaderAccessor, HttpHeaderAccessor>();
                services.AddSingleton<ICustomHeaderAccessor, CustomHeaderAccessor>();
    
                return services;
            }
        }

       有了服务的注册,我们还需要一个当前服务的实例提供者,一个ServiceProvider,如下

        public class ServiceProviderInstance
        {
            public static IServiceProvider Instance { get; set; }
        }

       我们在获取到当前进程的ServiceProvider后,保存到本地静态变量

        /// <summary>
        /// 基于IApplicationBuilder的扩展
        /// </summary>
        public static class ServiceProviderExtension
        {
            /// <summary>
            /// 给ServiceProviderInstance赋值ServiceProvider实例
            /// </summary>
            /// <param name="applicationBuilder">the IApplicationBuilder</param>
            /// <returns></returns>
            public static IApplicationBuilder UseServiceProviderBulider(this IApplicationBuilder applicationBuilder)
            {
                ServiceProviderInstance.Instance = applicationBuilder.ApplicationServices;
    
                return applicationBuilder;
            }
        }

      这里为什么能够在进程内保存一个静态的ServiceProvider?

      是因为在应用启动过程中,已经把Ioc容器构建好,把该注册的对象实例关系都已经保存在Ioc容器中了,这时ServiceProvider在进程内是不会有变化的了(动态注册暂时没在本篇文章的使用范围)。

      在这里已经给我们提示了另一种对象实例获取的方式,就是通过这个本地的ServiceProviderInstance来解析自己的所注册的对象实例,而不仅仅是通过构造函数(或者属性)注入的方式。

      使用

      看看我们如何完成整个流程的注册以及使用。

      首选需要在程序启动的时候注册。这里通过注册以及swagger的声明,用来做为本地的调试请求头添加

      public void ConfigureServices(IServiceCollection services)
            {           
                // 注册Swagger
                services.AddAppSwagger(document =>
                {
                    document.Description = "API";
    
                    document.OperationProcessors.Add(new OperationSecurityScopeProcessor("TenantId"));
    
                    document.DocumentProcessors.Add(new SecurityDefinitionAppender("TenantId", new NSwag.OpenApiSecurityScheme
                    {
                        Type = NSwag.OpenApiSecuritySchemeType.ApiKey,
                        Name = "TenantId",
                        In = NSwag.OpenApiSecurityApiKeyLocation.Header,
                        Description = "租户Id"
                    }));              
    
                services.AddHttpHeader();
            }

        获取系统的ServiceProvider并保存到本地。

      public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
            {
                if (env.IsDevelopment())
                {
                    app.UseDeveloperExceptionPage();
                }
    
                app.UseServiceProviderBuilder();
               
                // Swagger中间件
                app.UseAppSwagger();
    
                app.UseRouting();
    
                app.UseAuthorization();
    
                app.UseEndpoints(endpoints =>
                {
                    endpoints.MapControllers();
                });
            }

       定义应用服务的抽象类

     public abstract class ServiceBase 
        {
            /// <summary>
            /// 自定义头信息
            /// </summary>
            protected ICustomHeaderAccessor CustomHeader{ get; set; }
    
            /// <summary>
            /// ctor
            /// </summary>
            protected ServiceBase ()
            {
                CustomHeader = ServiceProviderInstance.Instance.GetRequiredService<ICustomHeaderAccessor>();
            }
        }

      使用层面上的赋值

      public class TestService : ServiceBase,ITestService
        {
            private readonly IRepository<Product> _productRepository;
    
            public TestService(IRepository<Product> productRepository)
            {
                _productRepository = productRepository;
            }
    
    
            public Result AddProduct(ProductDto dto)
            {
                _productRepository.Insert(new Product
                {
                    Name = dto.Name,
                    ...
                   TenantId = CustomHeader.TenantId
                });
            }
        }

      Swagger的本地调用

      为了方便调试,我们在上面的注册代码中写入了通过swagger中的请求头携带的http头部请求信息,在每次的本地调用中,可通过以下方式把我们需要传递的自定义信息填充进去。

      总结

      本文在ASP.NET Core的基础上,基于IHttpContextAccessor和一系列的辅助类实现Http自定义请求头策略,这里是从技术角度阐述了如何通过Http头实现自定义参数传递,文中的例子的使用场景是具有一定的前提的,各位朋友可以斟酌各自的使用场景。

  • 相关阅读:
    idea maven install java: 程序包不存在
    Window10取消文件默认打开方式
    @ModelAttribute与@RequestBody的区别
    python小知识
    CentOS下yum方式安装FFmpeg
    推荐一款可以直接下载浏览器sources资源的Chrome插件
    如何在python中使用Elasticsearch
    python logging模块“另一个程序正在使用此文件,进程无法访问。”问题解决办法
    Python的伪造数据生成器:Faker
    docker修改系统时间总结
  • 原文地址:https://www.cnblogs.com/lex-wu/p/13301272.html
Copyright © 2011-2022 走看看