用 JWT 机制实现验证的原理如下图:  认证服务器负责颁发 Token(相当于 JWT 值)和校验 Token 的合法性。

一、 相关概念

API 资源(API Resource):微博服务器接口、斗鱼弹幕服务器接口、斗鱼直播接口就是API 资源。

客户端(Client):Client 就是官方微博 android 客户端、官方微博 ios 客户端、第三方微博客户端、微博助手等。

身份资源(Identity Resource):就是用户。

一个用户可能使用多个客户端访问服务器;一个客户端也可能服务多个用户。封禁了一个客户端,所有用户都不能使用这个这个客户端访问服务器,但是可以使用其他客户端访问;封禁了一个用户,这个用户在所有设备上都不能访问,但是不影响其他用户。

二、 搭建 identity server 认证服务器

新建一个空的 web 项目 ID4.IdServer

  1. Nuget - Install-Package IdentityServer4

首先编写一个提供应用列表、账号列表的 Config 类

  1. using IdentityServer4.Models;
  2. using System.Collections.Generic;
  3. namespace ID4.IdServer
  4. {
  5. public class Config
  6. {
  7. /// <summary>
  8. /// 返回应用列表
  9. /// </summary>
  10. /// <returns></returns>
  11. public static IEnumerable<ApiResource> GetApiResources()
  12. {
  13. List<ApiResource> resources = new List<ApiResource>();
  14. //ApiResource第一个参数是应用的名字,第二个参数是描述
  15. resources.Add(new ApiResource("MsgAPI", "消息服务API"));
  16. resources.Add(new ApiResource("ProductAPI", "产品API"));
  17. return resources;
  18. }
  19. /// <summary>
  20. /// 返回账号列表
  21. /// </summary>
  22. /// <returns></returns>
  23. public static IEnumerable<Client> GetClients()
  24. {
  25. List<Client> clients = new List<Client>();
  26. clients.Add(new Client
  27. {
  28. ClientId = "clientPC1",//API账号、客户端Id
  29. AllowedGrantTypes = GrantTypes.ClientCredentials,
  30. ClientSecrets =
  31. {
  32. ".Sha256())//秘钥
  33. },
  34. AllowedScopes = { "MsgAPI", "ProductAPI" }//这个账号支持访问哪些应用
  35. });
  36. return clients;
  37. }
  38. }
  39. }

如果允许在数据库中配置账号等信息,那么可以从数据库中读取然后返回这些内容。疑问待解。

修改Startup.cs

  1. public void ConfigureServices(IServiceCollection services)
  2. {
  3. services.AddIdentityServer()
  4. .AddDeveloperSigningCredential()
  5. .AddInMemoryApiResources(Config.GetApiResources())
  6. .AddInMemoryClients(Config.GetClients());
  7. }
  8.  
  9. public void Configure(IApplicationBuilder app, IHostingEnvironment env)
  10. {
  11. app.UseIdentityServer();
  12. }

然后在 9500 端口启动

在 postman 里发出请求,获取 token http://localhost:9500/connect/token,发 Post 请求,表单请求内容(注意不是报文头):

client_id=clientPC1   client_secret=123321   grant_type=client_credentials

把返回的 access_token 留下来后面用(注意有有效期)。  注意,其实不应该让客户端直接去申请 token,这只是咱演示,后面讲解正确做法。

三、搭建 Ocelot 服务器项目

空 Web 项目,项目名 ID4.Ocelot1

nuget 安装 IdentityServer4、Ocelot

编写配置文件 Ocelot.json(注意设置【如果较新则】)

  1. {
  2. "ReRoutes": [
  3. {
  4. "DownstreamPathTemplate": "/api/{url}",
  5. "DownstreamScheme": "http",
  6. "UpstreamPathTemplate": "/MsgService/{url}",
  7. "UpstreamHttpMethod": ["Get", "Post"],
  8. "ServiceName": "MsgService",
  9. "LoadBalancerOptions": {
  10. "Type": "RoundRobin"
  11. },
  12. "UseServiceDiscovery": true,
  13. "AuthenticationOptions": {
  14. "AuthenticationProviderKey": "MsgKey",
  15. "AllowedScopes": []
  16. }
  17. },
  18. {
  19. "DownstreamPathTemplate": "/api/{url}",
  20. "DownstreamScheme": "http",
  21. "UpstreamPathTemplate": "/ProductService/{url}",
  22. "UpstreamHttpMethod": ["Get", "Post"],
  23. "ServiceName": "ProductService",
  24. "LoadBalancerOptions": {
  25. "Type": "RoundRobin"
  26. },
  27. "UseServiceDiscovery": true,
  28. "AuthenticationOptions": {
  29. "AuthenticationProviderKey": "ProductKey",
  30. "AllowedScopes": []
  31. }
  32. }
  33. ],
  34. "GlobalConfiguration": {
  35. "ServiceDiscoveryProvider": {
  36. "Host": "localhost",
  37.  
  38. }
  39. }
  40. }

把/MsgService 访问的都转给消息后端服务器(使用Consul进行服务发现)。也可以把Identity Server配置到Ocelot,但是我们不做,后边会讲为什么不放。

Program.cs 的 CreateWebHostBuilder 中加载 Ocelot.json

  1. .ConfigureAppConfiguration((hostingContext, builder) =>
  2. {
  3. builder.AddJsonFile("Ocelot.json",false, true);
  4. })

修改 Startup.cs 让 Ocelot 能够访问 Identity Server 进行 Token 的验证

  1. using System;
  2. using IdentityServer4.AccessTokenValidation;
  3. using Microsoft.AspNetCore.Builder;
  4. using Microsoft.AspNetCore.Hosting;
  5. using Microsoft.Extensions.DependencyInjection;
  6. using Ocelot.DependencyInjection;
  7. using Ocelot.Middleware;
  8. namespace ID4.Ocelot1
  9. {
  10. public class Startup
  11. {
  12. public void ConfigureServices(IServiceCollection services)
  13. {
  14. //指定Identity Server的信息
  15. Action<IdentityServerAuthenticationOptions> isaOptMsg = o =>
  16. {
  17. o.Authority = "http://localhost:9500";
  18. o.ApiName = "MsgAPI";//要连接的应用的名字
  19. o.RequireHttpsMetadata = false;
  20. o.SupportedTokens = SupportedTokens.Both;
  21. o.ApiSecret = "123321";//秘钥
  22. };
  23. Action<IdentityServerAuthenticationOptions> isaOptProduct = o =>
  24. {
  25. o.Authority = "http://localhost:9500";
  26. o.ApiName = "ProductAPI";//要连接的应用的名字
  27. o.RequireHttpsMetadata = false;
  28. o.SupportedTokens = SupportedTokens.Both;
  29. o.ApiSecret = "123321";//秘钥
  30. };
  31. services.AddAuthentication()
  32. //对配置文件中使用ChatKey配置了AuthenticationProviderKey=MsgKey
  33. //的路由规则使用如下的验证方式
  34. .AddIdentityServerAuthentication("MsgKey", isaOptMsg).AddIdentityServerAuthentication("ProductKey", isaOptProduct); services.AddOcelot();
  35. }
  36. // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
  37. public void Configure(IApplicationBuilder app, IHostingEnvironment env)
  38. {
  39. if (env.IsDevelopment())
  40. {
  41. app.UseDeveloperExceptionPage();
  42.  
  43. }
  44. app.UseOcelot().Wait();
  45. }
  46. }
  47. }

很显然我们可以让不同的服务采用不同的Identity Server。

启动 Ocelot 服务器,然后向 ocelot 请求/MsgService/SMS/Send_MI(报文体还是要传 json 数据),在请求头(不是报文体)里加上:

Authorization="Bearer "+上面 identityserver 返回的 accesstoken

如果返回 401,那就是认证错误。

Ocelot 会把 Authorization 值传递给后端服务器,这样在后端服务器可以用 IJwtDecoder 的这个不传递 key 的重载方法 IDictionary<string, object> DecodeToObject(string token),就可以在不验证的情况下获取 client_id 等信息。

也可以把 Identity Server 通过 Consul 进行服务治理。

Ocelot+Identity Server 实现了接口的权限验证,各个业务系统不需要再去做验证。

四、不能让客户端请求 token

上面是让客户端去请求 token,如果项目中这么搞的话,就把 client_id 特别是 secret 泄露给普通用户的。

正确的做法应该是,开发一个 token 服务,由这个服务来向 identity Server 请求 token,客户端向 token 服务发请求,把 client_id、secret 藏到这个 token 服务器上。当然这个服务器也要经过 Ocelot 转发。

五、用户名密码登录

如果 Api 和用户名、密码无关(比如系统内部之间 API 的调用),那么上面那样做就可以了,但是有时候需要用户身份验证的(比如 Android 客户端)。也就是在请求 token 的时候还要验证用户名密码,在服务中还可以获取登录用户信息。

修改的地方:

1、 ID4.IdServer 项目中增加类 ProfileService.cs

  1. using IdentityServer4.Models;
  2. using IdentityServer4.Services;
  3. using System.Linq;
  4. using System.Threading.Tasks;
  5. namespace ID4.IdServer
  6. {
  7. public class ProfileService : IProfileService
  8. {
  9. public async Task GetProfileDataAsync(ProfileDataRequestContext context)
  10. {
  11. var claims = context.Subject.Claims.ToList(); context.IssuedClaims = claims.ToList();
  12. }
  13. public async Task IsActiveAsync(IsActiveContext context)
  14. {
  15. context.IsActive = true;
  16. }
  17. }
  18. }

增加类 ResourceOwnerPasswordValidator.cs

  1. using IdentityServer4.Models;
  2. using IdentityServer4.Validation;
  3. using System.Security.Claims;
  4. using System.Threading.Tasks;
  5. namespace ID4.IdServer
  6. {
  7. public class ResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator
  8. {
  9. public async Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
  10. {
  11. //根据context.UserName和context.Password与数据库的数据做校验,判断是否合法
  12. ")
  13. {
  14. context.Result = "),           new Claim("RealName", "名字"),           new Claim("Email", "qq@qq.com") });
  15. }
  16. else
  17. {
  18. //验证失败
  19. context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "invalid custom credential");
  20. }
  21. }
  22. }
  23. }

当然这里的用户名密码是写死的,可以在项目中连接自己的用户数据库进行验证。claims 中可以放入多组用户的信息,这些信息都可以在业务系统中获取到。

Config.cs

修改一下,主要是把GetClients中的AllowedGrantTypes属性值改为GrantTypes.ResourceOwnerPassword,

并且在AllowedScopes中加入

IdentityServerConstants.StandardScopes.OpenId, //必须要添加,否则报forbidden错误

IdentityServerConstants.StandardScopes.Profile

修改后的 Config.cs

  1. using System.Collections.Generic;
  2. using IdentityServer4;
  3. using IdentityServer4.Models;
  4. namespace ID4.IdServer
  5. {
  6. public class Config
  7. {
  8. /// <summary>
  9. /// 返回应用列表
  10. /// </summary>
  11. /// <returns></returns>
  12. public static IEnumerable<ApiResource> GetApiResources()
  13. {
  14. List<ApiResource> resources = new List<ApiResource>();
  15. //ApiResource第一个参数是应用的名字,第二个参数是描述
  16. resources.Add(new ApiResource("MsgAPI", "消息服务API"));
  17. resources.Add(new ApiResource("ProductAPI", "产品API"));
  18. return resources;
  19. }
  20.  
  21. /// <summary>
  22. /// 返回客户端账号列表
  23. /// </summary>
  24. /// <returns></returns>
  25. public static IEnumerable<Client> GetClients()
  26. {
  27. List<Client> clients = new List<Client>(); clients.Add(new Client
  28. {
  29. ClientId = "clientPC1",//API账号、客户端Id
  30. AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
  31. ClientSecrets =
  32. {
  33. ".Sha256())//秘钥
  34. },
  35. AllowedScopes = { "MsgAPI","ProductAPI",IdentityServerConstants.StandardScopes.OpenId, //必须要添加,否则报forbidden错误
  36. IdentityServerConstants.StandardScopes.Profile
  37. }//这个账号支持访问哪些应用
  38. }); return clients;
  39. }
  40. }
  41. }

Startup.cs 的 ConfigureServices 修改为

  1. public void ConfigureServices(IServiceCollection services)
  2. {
  3. var idResources = new List<IdentityResource>
  4. {
  5. new IdentityResources.OpenId(), //必须要添加,否则报无效的 scope 错误
  6. new IdentityResources.Profile()
  7. };
  8. services.AddIdentityServer()
  9. .AddDeveloperSigningCredential()
  10. .AddInMemoryIdentityResources(idResources)
  11. .AddInMemoryApiResources(Config.GetApiResources())
  12. .AddInMemoryClients(Config.GetClients())//
  13. .AddResourceOwnerValidator<ResourceOwnerPasswordValidator>()
  14. .AddProfileService<ProfileService>();
  15. }

主要是增加了 AddInMemoryIdentityResources 、 AddResourceOwnerValidator 、AddProfileService

2、 修改业务系统

以 MsgService 为例

  1. Nuget -> Install-Package IdentityServer4.AccessTokenValidation

然后 Startup.cs 的 ConfigureServices 中增加

  1. services.AddAuthentication("Bearer")
  2. .AddIdentityServerAuthentication(options =>
  3. {
  4. options.Authority = "http://localhost:9500";//identity server 地址
  5. options.RequireHttpsMetadata = false;
  6. });

Startup.cs 的 Configure 中增加

  1. app.UseAuthentication();

3、 请求 token 把报文头中的 grant_type 值改为 password,报文头增加 username、password 为用户名、密码。

像之前一样用返回的 access_token传递给请求的Authorization
中,在业务系统的
User中就可以获取到 ResourceOwnerPasswordValidator
中为用户设置的
claims 等信息了。

  1. public void Send_MI(dynamic model)
  2. {
  3. string name = this.User.Identity.Name;//读取的就是"Name"这个特殊的 Claims 的值
  4. string userId = this.User.FindFirst("UserId").Value;    string realName = this.User.FindFirst("RealName").Value;    string email = this.User.FindFirst("Email").Value;
  5. Console.WriteLine($"name={name},userId={userId},realName={realName},email={email}");
  6. Console.WriteLine($"通过小米短信接口向{model.phoneNum}发送短信{model.msg}");
  7. }

4、 独立登录服务器解决上面提到的“不能让客户端接触到 client_id、secret 的问题”

开发一个服务应用 LoginService

  1. public class RequestTokenParam
  2. {
  3. public string username { get; set; }
  4. public string password { get; set; }
  5. }
  1. using System.Collections.Generic;
  2. using System.Net.Http;
  3. using System.Threading.Tasks;
  4. using Microsoft.AspNetCore.Mvc;
  5. namespace LoginService.Controllers
  6. {
  7. [Route("api/[controller]")]
  8. [ApiController]
  9. public class LoginController : ControllerBase
  10. {
  11. [HttpPost]
  12. public async Task<ActionResult> RequestToken(RequestTokenParam model)
  13. {
  14. Dictionary<string, string> dict = new Dictionary<string, string>();
  15. dict["client_id"] = "clientPC1";
  16. dict[";
  17. dict["grant_type"] = "password";
  18. dict["username"] = model.username;
  19. dict["password"] = model.password;
  20. //由登录服务器向IdentityServer发请求获取Token
  21. using (HttpClient http = new HttpClient())     using (var content = new FormUrlEncodedContent(dict))
  22. {
  23. var msg = await http.PostAsync("http://localhost:9500/connect/token", content);           string result = await msg.Content.ReadAsStringAsync();          return Content(result, "application/json");
  24. }
  25. }
  26. }
  27. }

这样客户端只要向 LoginService 的 /api/Login/ 发请求带上 json 报文体

{username:"yzk",password:"123"}即可。客户端就不知道 client_secret 这些机密信息了。

把 LoginService 配置到 Ocelot 中。

参考文章:https://www.cnblogs.com/jaycewu/p/7791102.html

注:此文章是我看杨中科老师的.Net Core微服务第二版和.Net Core微服务第二版课件整理出来的

(10)学习笔记 ) ASP.NET CORE微服务 Micro-Service ---- Ocelot+Identity Server的更多相关文章

  1. 【新书推荐】《ASP.NET Core微服务实战:在云环境中开发、测试和部署跨平台服务》 带你走近微服务开发

    <ASP.NET Core 微服务实战>译者序:https://blog.jijiechen.com/post/aspnetcore-microservices-preface-by-tr ...

  2. Asp.Net Core微服务再体验

    ASP.Net Core的基本配置 .在VS中调试的时候有很多修改Web应用运行端口的方法.但是在开发.调试微服务应用的时候可能需要同时在不同端口上开启多个服务器的实例,因此下面主要看看如何通过命令行 ...

  3. ASP.NET Core 微服务初探[1]:服务发现之Consul

    ASP.NET Core 微服务初探[1]:服务发现之Consul   在传统单体架构中,由于应用动态性不强,不会频繁的更新和发布,也不会进行自动伸缩,我们通常将所有的服务地址都直接写在项目的配置文件 ...

  4. .NET Core微服务之基于Ocelot+Butterfly实现分布式追踪

    Tip: 此篇已加入.NET Core微服务基础系列文章索引 一.什么是Tracing? 微服务的特点决定了功能模块的部署是分布式的,以往在单应用环境下,所有的业务都在同一个服务器上,如果服务器出现错 ...

  5. .NET Core 微服务—API网关(Ocelot) 教程 [三]

    前言: 前一篇文章<.NET Core 微服务—API网关(Ocelot) 教程 [二]>已经让Ocelot和目录api(Api.Catalog).订单api(Api.Ordering)通 ...

  6. .NET Core微服务二:Ocelot API网关

    .NET Core微服务一:Consul服务中心 .NET Core微服务二:Ocelot API网关 .NET Core微服务三:polly熔断与降级 本文的项目代码,在文章结尾处可以下载. 本文使 ...

  7. .NET Core 微服务—API网关(Ocelot) 教程 [二]

    上篇文章(.NET Core 微服务—API网关(Ocelot) 教程 [一])介绍了Ocelot 的相关介绍. 接下来就一起来看如何使用,让它运行起来. 环境准备 为了验证Ocelot 网关效果,我 ...

  8. (8)学习笔记 ) ASP.NET CORE微服务 Micro-Service ---- Ocelot网关(Api GateWay)

    说到现在现有微服务的几点不足: 1) 对于在微服务体系中.和 Consul 通讯的微服务来讲,使用服务名即可访问.但是对于手 机.web 端等外部访问者仍然需要和 N 多服务器交互,需要记忆他们的服务 ...

  9. (7)学习笔记 ) ASP.NET CORE微服务 Micro-Service ---- 利用Polly+AOP+依赖注入封装的降级框架

    创建简单的熔断降级框架 要达到的目标是: 参与降级的方法参数要一样,当HelloAsync执行出错的时候执行HelloFallBackAsync方法. public class Person { [H ...

随机推荐

  1. system.img镜像转换为system.new.dat + system.transfer.list

    android 8.1上面验证,支持所有的android版本,直接放到sdk中执行即可.   img2sdat.py #!/usr/bin/env python #coding=utf-8   imp ...

  2. MongoDB的基本操作:服务端启动,客户端连接,CRUD操作

    本文内容: MongoDB的介绍 MongoDB服务端的启动 MongoDB客户端连接 SQL与MongoDB相关概念解释 什么是BSON 数据库操作 集合操作 文档操作 测试环境:win10 软件版 ...

  3. axure元件库导入后重启程序元件库消失问题

    把元件库文件放在我的文档里的 \Documents\Axure\元件库 就可以了,重新启动程序不会消失

  4. 使用虚拟环境virtualenv/Virtualenvwrapper隔离多个python

    virtualenv 系统中的多个python混用会导致$PYTHONPATH混乱,或者各个工程对于package的版本要求不同等等情况.有一个简单的解决方案就是用virtualenv来隔离多个pyt ...

  5. JMeter乱码常见的解决方案

    方法一.直接将JMeter中http请求中Content encoding改为utf-8 方法二.编辑JMeter安装目录:apache-jmeter-3.2\bin中的jmeter.properti ...

  6. Git命令实现本地文件推送到git仓库

    前提: ①确定本机(windows环境)已经安装git(https://git-scm.com/downloads) ②建立好远程Git仓库   1.在你想推送的文件夹下:右键→选择Git Bath ...

  7. 房企大裁员;争议贺建奎;破产阴影下的ofo:4星|《财经》第29期

    <财经>2018年第29期 总第546期 旬刊 高水平的财经杂志.本期重要话题有:1:房企大裁员;2:争议贺建奎;3:破产阴影下的ofo; 总体评价4星,非常好. 以下是书中一些内容的摘抄 ...

  8. 学生&部门智能匹配程序

    Github链接 结对成员: 031502308 付逸豪 031502113 胡俊钦 数据生成 样例链接: 来点我啊 由于在生成数据的时候做了很多符合实际情况的限制,所以生成的数据都挺好的,就随便选了 ...

  9. JFreeChart柱状图单组柱子的不同颜色显示

    JFreeChart柱状图中单组柱子用不同颜色来显示的实现方法是自定义一个Renderer来继承BarRenderer类,然后重载getItemPaint(int i,int j)方法. 实现效果如下 ...

  10. (12)Python异常