.NET Core微服务之基于Ocelot+IdentityServer实现统一验证与授权

2023-05-16

一、案例结构总览

640?wx_fmt=png

  这里,假设我们有两个客户端(一个Web网站,一个移动App),他们要使用系统,需要先向IdentityService进行Login以进行验证并获取Token,在IdentityService的验证过程中会访问数据库以验证。然后再带上Token通过API网关去访问具体的API Service。这里我们的IdentityService基于IdentityServer4开发,它具有统一登录验证和授权的功能。当然,我们也可以将统一登录验证独立出来,写成一个单独的API Service,托管在API网关中,这里我不想太麻烦,便直接将其也写在了IdentityService中。

二、改写API Gateway

  这里主要基于前两篇已经搭好的API Gateway进行改写,如不熟悉,可以先浏览前两篇文章:Part 1和Part 2。

2.1 配置文件的改动

640?wx_fmt=gif


  ......  
  "AuthenticationOptions": {    "AuthenticationProviderKey": "ClientServiceKey",    "AllowedScopes": []
  }
  ......  
  "AuthenticationOptions": {    "AuthenticationProviderKey": "ProductServiceKey",    "AllowedScopes": []
  }
  ......    

640?wx_fmt=gif

  上面分别为两个示例API Service增加Authentication的选项,为其设置ProviderKey。下面会对不同的路由规则设置的ProviderKey设置具体的验证方式。

2.2 改写StartUp类

640?wx_fmt=gif


    public void ConfigureServices(IServiceCollection services)
    {        // IdentityServer
        #region IdentityServerAuthenticationOptions => need to refactor
        Action<IdentityServerAuthenticationOptions> isaOptClient = option =>
            {
                option.Authority = Configuration["IdentityService:Uri"];
                option.ApiName = "clientservice";
                option.RequireHttpsMetadata = Convert.ToBoolean(Configuration["IdentityService:UseHttps"]);
                option.SupportedTokens = SupportedTokens.Both;
                option.ApiSecret = Configuration["IdentityService:ApiSecrets:clientservice"];
            };

        Action<IdentityServerAuthenticationOptions> isaOptProduct = option =>
        {
            option.Authority = Configuration["IdentityService:Uri"];
            option.ApiName = "productservice";
            option.RequireHttpsMetadata = Convert.ToBoolean(Configuration["IdentityService:UseHttps"]);
            option.SupportedTokens = SupportedTokens.Both;
            option.ApiSecret = Configuration["IdentityService:ApiSecrets:productservice"];
        }; 
        #endregion

        services.AddAuthentication()
            .AddIdentityServerAuthentication("ClientServiceKey", isaOptClient)
            .AddIdentityServerAuthentication("ProductServiceKey", isaOptProduct);        // Ocelot        services.AddOcelot(Configuration);
        ......       
    }  

640?wx_fmt=gif

  这里的ApiName主要对应于IdentityService中的ApiResource中定义的ApiName。这里用到的配置文件定义如下:

640?wx_fmt=gif View Code

  这里的定义方式,我暂时还没想好怎么重构,不过肯定是需要重构的,不然这样一个一个写比较繁琐,且不利于配置。

三、新增IdentityService

这里我们会基于之前基于IdentityServer的两篇文章,新增一个IdentityService,不熟悉的朋友可以先浏览一下Part 1和Part 2。

3.1 准备工作

  新建一个ASP.NET Core Web API项目,绑定端口5100,NuGet安装IdentityServer4。配置好证书,并设置其为“较新则复制”,以便能够在生成目录中读取到。

3.2 定义一个InMemoryConfiguration用于测试

640?wx_fmt=gif


    /// <summary>
    /// One In-Memory Configuration for IdentityServer => Just for Demo Use    /// </summary>
    public class InMemoryConfiguration
    {        public static IConfiguration Configuration { get; set; }        /// <summary>
        /// Define which APIs will use this IdentityServer        /// </summary>
        /// <returns></returns>
        public static IEnumerable<ApiResource> GetApiResources()
        {            return new[]
            {                new ApiResource("clientservice", "CAS Client Service"),                new ApiResource("productservice", "CAS Product Service"),                new ApiResource("agentservice", "CAS Agent Service")
            };
        }        /// <summary>
        /// Define which Apps will use thie IdentityServer        /// </summary>
        /// <returns></returns>
        public static IEnumerable<Client> GetClients()
        {            return new[]
            {                new Client
                {
                    ClientId = "cas.sg.web.nb",
                    ClientName = "CAS NB System MPA Client",
                    ClientSecrets = new [] { new Secret("websecret".Sha256()) },
                    AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
                    AllowedScopes = new [] { "clientservice", "productservice",
                        IdentityServerConstants.StandardScopes.OpenId,
                        IdentityServerConstants.StandardScopes.Profile }
                },                new Client
                {
                    ClientId = "cas.sg.mobile.nb",
                    ClientName = "CAS NB System Mobile App Client",
                    ClientSecrets = new [] { new Secret("mobilesecret".Sha256()) },
                    AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
                    AllowedScopes = new [] { "productservice",
                        IdentityServerConstants.StandardScopes.OpenId,
                        IdentityServerConstants.StandardScopes.Profile }
                },                new Client
                {
                    ClientId = "cas.sg.spa.nb",
                    ClientName = "CAS NB System SPA Client",
                    ClientSecrets = new [] { new Secret("spasecret".Sha256()) },
                    AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
                    AllowedScopes = new [] { "agentservice", "clientservice", "productservice",
                        IdentityServerConstants.StandardScopes.OpenId,
                        IdentityServerConstants.StandardScopes.Profile }
                },                new Client
                {
                    ClientId = "cas.sg.mvc.nb.implicit",
                    ClientName = "CAS NB System MVC App Client",
                    AllowedGrantTypes = GrantTypes.Implicit,
                    RedirectUris = { Configuration["Clients:MvcClient:RedirectUri"] },
                    PostLogoutRedirectUris = { Configuration["Clients:MvcClient:PostLogoutRedirectUri"] },
                    AllowedScopes = new [] {
                        IdentityServerConstants.StandardScopes.OpenId,
                        IdentityServerConstants.StandardScopes.Profile,                        "agentservice", "clientservice", "productservice"
                    },                    //AccessTokenLifetime = 3600, // one hour
                    AllowAccessTokensViaBrowser = true // can return access_token to this client                }
            };
        }        /// <summary>
        /// Define which IdentityResources will use this IdentityServer        /// </summary>
        /// <returns></returns>
        public static IEnumerable<IdentityResource> GetIdentityResources()
        {            return new List<IdentityResource>
            {                new IdentityResources.OpenId(),                new IdentityResources.Profile(),
            };
        }
    }  

640?wx_fmt=gif

  这里使用了上一篇的内容,不再解释。实际环境中,则应该考虑从NoSQL或数据库中读取。

3.3 定义一个ResourceOwnerPasswordValidator

  在IdentityServer中,要实现自定义的验证用户名和密码,需要实现一个接口:IResourceOwnerPasswordValidator

640?wx_fmt=gif


    public class ResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator
    {        private ILoginUserService loginUserService;        public ResourceOwnerPasswordValidator(ILoginUserService _loginUserService)
        {            this.loginUserService = _loginUserService;
        }        public Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
        {
            LoginUser loginUser = null;            bool isAuthenticated = loginUserService.Authenticate(context.UserName, context.Password, out loginUser);            if (!isAuthenticated)
            {
                context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "Invalid client credential");
            }            else
            {
                context.Result = new GrantValidationResult(
                    subject : context.UserName,
                    authenticationMethod : "custom",
                    claims : new Claim[] {                        new Claim("Name", context.UserName),                        new Claim("Id", loginUser.Id.ToString()),                        new Claim("RealName", loginUser.RealName),                        new Claim("Email", loginUser.Email)
                    }
                );
            }            return Task.CompletedTask;
        }
    }  

640?wx_fmt=gif

  这里的ValidateAsync方法中(你也可以把它写成异步的方式,这里使用的是同步的方式),会调用EF去访问数据库进行验证,数据库的定义如下(密码应该做加密,这里只做demo,没用弄):

  640?wx_fmt=png

  至于EF部分,则是一个典型的简单的Service调用Repository的逻辑,下面只贴Repository部分:

640?wx_fmt=gif View Code

  其他具体逻辑请参考示例代码。

3.4 改写StarUp类

640?wx_fmt=gif


    public void ConfigureServices(IServiceCollection services)
    {        // IoC - DbContext
        services.AddDbContextPool<IdentityDbContext>(
            options => options.UseSqlServer(Configuration["DB:Dev"]));        // IoC - Service & Repository
        services.AddScoped<ILoginUserService, LoginUserService>();
        services.AddScoped<ILoginUserRepository, LoginUserRepository>();        // IdentityServer4
        string basePath = PlatformServices.Default.Application.ApplicationBasePath;
        InMemoryConfiguration.Configuration = this.Configuration;
        services.AddIdentityServer()
            .AddSigningCredential(new X509Certificate2(Path.Combine(basePath,
                Configuration["Certificates:CerPath"]),
                Configuration["Certificates:Password"]))            //.AddTestUsers(InMemoryConfiguration.GetTestUsers().ToList())            .AddInMemoryIdentityResources(InMemoryConfiguration.GetIdentityResources())
            .AddInMemoryApiResources(InMemoryConfiguration.GetApiResources())
            .AddInMemoryClients(InMemoryConfiguration.GetClients())            .AddResourceOwnerValidator<ResourceOwnerPasswordValidator>()
            .AddProfileService<ProfileService>();
        ......
    }  

640?wx_fmt=gif

  这里高亮的是新增的部分,为了实现自定义验证。关于ProfileService的定义如下:

640?wx_fmt=gif View Code

3.5 新增统一Login入口

  这里新增一个LoginController:

640?wx_fmt=gif


    [Produces("application/json")]
    [Route("api/Login")]    public class LoginController : Controller
    {        private IConfiguration configuration;        public LoginController(IConfiguration _configuration)
        {
            configuration = _configuration;
        }

        [HttpPost]        public async Task<ActionResult> RequestToken([FromBody]LoginRequestParam model)
        {
            Dictionary<string, string> dict = new Dictionary<string, string>();
            dict["client_id"] = model.ClientId;
            dict["client_secret"] = configuration[$"IdentityClients:{model.ClientId}:ClientSecret"];
            dict["grant_type"] = configuration[$"IdentityClients:{model.ClientId}:GrantType"];
            dict["username"] = model.UserName;
            dict["password"] = model.Password;            using (HttpClient http = new HttpClient())            using (var content = new FormUrlEncodedContent(dict))
            {                var msg = await http.PostAsync(configuration["IdentityService:TokenUri"], content);                if (!msg.IsSuccessStatusCode)
                {                    return StatusCode(Convert.ToInt32(msg.StatusCode));
                }                string result = await msg.Content.ReadAsStringAsync();                return Content(result, "application/json");
            }
        }
    }  

640?wx_fmt=gif

  这里假设客户端会传递用户名,密码以及客户端ID(ClientId,比如上面InMemoryConfiguration中的cas.sg.web.nb或cas.sg.mobile.nb)。然后构造参数再调用connect/token接口进行身份验证和获取token。这里将client_secret等机密信息封装到了服务器端,无须客户端传递(对于机密信息一般也不会让客户端知道):

640?wx_fmt=gif


  "IdentityClients": {    "cas.sg.web.nb": {      "ClientSecret": "websecret",      "GrantType": "password"
    },    "cas.sg.mobile.nb": {      "ClientSecret": "mobilesecret",      "GrantType": "password"
    }
  }  

640?wx_fmt=gif

四、改写业务API Service

4.1 ClientService

  (1)安装IdentityServer4.AccessTokenValidation

NuGet>Install-Package IdentityServer4.AccessTokenValidation

  (2)改写StartUp类

640?wx_fmt=gif


    public IServiceProvider ConfigureServices(IServiceCollection services)
    {
        ......        // IdentityServer
        services.AddAuthentication(Configuration["IdentityService:DefaultScheme"])
            .AddIdentityServerAuthentication(options =>
            {
                options.Authority = Configuration["IdentityService:Uri"];
                options.RequireHttpsMetadata = Convert.ToBoolean(Configuration["IdentityService:UseHttps"]);
            });

        ......
    }  

640?wx_fmt=gif

  这里配置文件的定义如下:

640?wx_fmt=gif


  "IdentityService": {    "Uri": "http://localhost:5100",    "DefaultScheme":  "Bearer",    "UseHttps": false,    "ApiSecret": "clientsecret"
  }  

640?wx_fmt=gif

4.2 ProductService

  与ClientService一致,请参考示例代码。

五、测试

5.1 测试Client: cas.sg.web.nb

  (1)统一验证&获取token

  640?wx_fmt=png

  (2)访问clientservice (by API网关)

  640?wx_fmt=png

  (3)访问productservice(by API网关)

  640?wx_fmt=png

5.2 测试Client: cas.sg.mobile.nb

  由于在IdentityService中我们定义了一个mobile的客户端,但是其访问权限只有productservice,所以我们来测试一下:

  (1)统一验证&获取token

  640?wx_fmt=png

  (2)访问ProductService(by API网关)

  640?wx_fmt=png

  (3)访问ClientService(by API网关) => 401 Unauthorized

  640?wx_fmt=png

六、小结

  本篇主要基于前面Ocelot和IdentityServer的文章的基础之上,将Ocelot和IdentityServer进行结合,通过建立IdentityService进行统一的身份验证和授权,最后演示了一个案例以说明如何实现。不过,本篇实现的Demo还存在诸多不足,比如需要重构的代码较多如网关中各个Api的验证选项的注册,没有对各个请求做用户角色和权限的验证等等,相信随着研究和深入的深入,这些都可以逐步解决。后续会探索一下数据一致性的基本知识以及框架使用,到时再做一些分享。

示例代码

  Click Here => 点我进入GitHub

参考资料

  杨中科,《.NET Core微服务介绍课程》

  640?wx_fmt=jpeg


本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

.NET Core微服务之基于Ocelot+IdentityServer实现统一验证与授权 的相关文章

  • Semi-Supervised and Self-Supervised Classification with Multi-View Graph Neural Networks

    摘要 图神经网络在图结构数据中取得了很好的效果但是大多数的模型使用的还是叫浅层的结构 xff0c 当模型层数加深时很容易过平滑 本文基于多视图来聚合更多的信息 我们首先设计两个互补的视图来描述全局结构和节点特征相似性 xff0c 然后使用注
  • GCC: Graph Contrastive Coding for Graph Neural Network Pre-Training

    摘要 目前图表示学习在许多任务上取得了很好的效果但是都是关注于具体领域的并不具有迁移性 本文借鉴预训练思想 xff0c 设计了一个自监督图神经网络框架来在多个网络中捕获一般化的网络拓扑结构属性 我们设计的预训练任务是在多个网络之间判别子图实
  • Graph Contrastive Learning with Adaptive Augmentation

    摘要 对比学习在无监督图表示学习中取得了很好的效果 xff0c 大部分图对比学习首先对输入图做随机增强生成两个视图然后最大化两个视图表示的一致性 其中 xff0c 图上的增强方式是非常重要的部分鲜有人探索 我们认为数据增强模式应该保留图固有
  • A Survey on Graph Structure Learning: Progress and Opportunities

    文章目录 摘要引言预备知识GSL pipline Graph Structure ModelingMetric based ApproachesNeural ApproachesDirect Approaches Postprocessin
  • 图构造总结-Graph‑based semi‑supervised learning via improving the quality of the graph dynamically

    前言 本博文主要对论文中提到的图构造方法进行梳理 xff0c 论文自己提出的模型并未介绍 xff0c 感兴趣的可以阅读原文 摘要 基于图的半监督学习GSSL主要包含两个过程 xff1a 图的构建和标签推测 传统的GSSL中这两个过程是完全独
  • 超图构造综述,Hypergraph Learning: Methods and Practices

    文章目录 摘要引言基础知识Hypergraph GenerationDistance based hypergraph generationRepresentation based hypergraph generationAttribut
  • 图论基础知识总结

    文章目录 图的概念路图的代数表示邻接矩阵可达矩阵完全关联矩阵拉普拉斯矩阵对称归一化拉普拉斯矩阵随机游走归一化拉普拉斯矩阵 欧拉图与汉密尔顿图平面图对偶与着色数与生成树最小生成树算法 xff1a 根树图的存储邻接矩阵邻接表十字链表邻接多重表
  • 图增强与图对比学习

    文章目录 对比学习数据增强基于特征的增强基于结构的增强基于采样的增强自适应的增强 代理任务同尺度对比跨尺度对比 目标函数参考 CSDN排版太垃圾了 xff0c 点此连接去知乎看吧 xff01 xff01 xff01 xff01 xff01
  • K-Core, K-Shell & K-Crust傻傻分不清楚

    K Core算法是用于在图中寻找符合一定紧密关系条件的子图结构的算法 xff0c 通常用于进行子图划分 xff0c 去除不重要的结点 参考论文k core Theories and applications ScienceDirect K
  • 社区发现算法总结

    图划分指将网络顶点划分为指定规模 xff0c 指定数量的非重叠群组 xff0c 使得群组之间的边数最小 图划分时 xff0c 群组的数量和规模是固定的 社区发现不同于图划分的是网络群组的数量和规模不是由实验者确定的 xff0c 而是由网络本
  • 机器学习面经--常见问题

    csdn的排版真的太垃圾了 xff0c 想看的移步知乎 1 xff0c Normalization 数据标准化处理主要包括数据同趋化处理和无量纲化处理两个方面 数据同趋化处理主要解决不同性质数据问题 xff0c 对不同性质指标直接加总不能正
  • 设计模式之创建型模式

    文章目录 创建型模式 Creational Pattern 1 1单例模式 Singleton Pattern 1 2工厂模式 Factory Pattern 1 3抽象工厂模式 Abstract Factory Pattern 1 4建造
  • 设计模式之行为型模式

    文章目录 行为型模式 Behavioral Pattern 1 观察者模式 Observer Pattern 2 中介者模式 Mediator Pattern 3 访问者模式 Visitor Pattern 4 状态模式 State Pat
  • 设计模式之结构型模式

    文章目录 结构型模式 Structural Pattern 1 适配器模式 Adapter Wrapper Pattern 2 桥接模式 Bridge Pattern 3 装饰模式 Decorator Pattern 4 外观模式 Faca
  • IE8 中"HTML Parsing Error:Unable to modify the parent container element before the child element is c

    一 又涨见识了 IE8报下面错误 xff0c 而且是我的机器不报 xff0c 同事的机器报 xff0c 试了4台 xff0c 两个报 xff0c 两个不报 xff0c IE版本都一样 xff0c 没想明白 解决 xff1a 1 查看是否有未
  • CentOS7, CentOS8 firewalld docker 端口映射问题,firewall开放端口后,还是不能访问,解决方案

    宿主机ip 192 168 31 19 docker run itd name tomcat p 8080 8080 tomcat usr local apache tomcat 9 0 30 bin startup sh 防火墙放开808
  • 2.3语料库NLTK数据包下载及安装

    NLTK xff08 Natural Language Toolkit xff09 是一个用于构建处理自然语言数据的Python应用开源平台 NLTK提供了超过50多个素材库和词库资源的易用接口 xff0c 涵盖了分词 词性标注 命名实体识
  • android:使用audiotrack 类播放wav文件

    参考 xff1a http mindtherobot com blog 624 android audio play an mp3 file on an audiotrack http baike baidu com view 14471
  • dependencyManagement和dependencies区别

    引语 xff1a 平时java项目中我们经常使用maven xff0c 再多模块的项目中会经常见到父项目和子项目中的dependencyManagement xff0c dependencies xff0c 今天我们就来介绍一下它们的区别
  • 初用AndroidStudio建立第一个项目遇到的Error以及解决办法

    初用AndroidStudio建立第一个项目遇到的Error以及解决办法 今天是使用AndroidStudio的第一天 xff0c 初次创建HelloWorld 遇到了两个小错误 xff0c 下面分享给大家 xff0c 方便大家处理错误 x

随机推荐