.Net Core微服务入门全纪录(七)——IdentityServer4

前言

上一篇【.Net Core微服务入门全纪录(六)——EventBus-事件总线】中使用CAP完成了一个简单的Eventbus,实现了服务之间的解耦和异步调用,并且做到数据的最终一致性。这一篇将使用IdentityServer4来搭建一个鉴权中心,来完成授权认证相关的功能。

IdentityServer4官方文档:https://identityserver4.readthedocs.io/

鉴权中心

创建ids4项目

关于IdentityServer4的基本介绍和模板安装可以看一下我的另一篇博客【IdentityServer4 4.x版本 配置Scope的正确姿势】,下面直接从创建项目开始。

来到我的项目目录下执行:dotnet new is4inmem --name IDS4.AuthCenter

image-20200629210341489

执行完成后会生成以下文件:

image-20200629210446718

用vs2019打开之前的解决方案,把刚刚创建的ids项目添加进来:

image-20200629210933318

将此项目设为启动项,先运行看一下效果:

image-20200629211848802

image-20200629212102283

项目正常运行,下面需要结合我们的业务稍微修改一下默认代码。

鉴权中心配置

修改Startup的ConfigureServices方法:

1// in-memory, code config 2builder.AddInMemoryIdentityResources(Config.IdentityResources); 3builder.AddInMemoryApiScopes(Config.ApiScopes); 4builder.AddInMemoryApiResources(Config.ApiResources); 5builder.AddInMemoryClients(Config.Clients);

Config类:

1public static class Config 2{ 3 public static IEnumerable<IdentityResource> IdentityResources => 4 new IdentityResource[] 5 { 6 new IdentityResources.OpenId(), 7 new IdentityResources.Profile(), 8 }; 9 10 public static IEnumerable<ApiResource> ApiResources => 11 new ApiResource[] 12 { 13 new ApiResource("orderApi","订单服务") 14 { 15 ApiSecrets ={ new Secret("orderApi secret".Sha256()) }, 16 Scopes = { "orderApiScope" } 17 }, 18 new ApiResource("productApi","产品服务") 19 { 20 ApiSecrets ={ new Secret("productApi secret".Sha256()) }, 21 Scopes = { "productApiScope" } 22 } 23 }; 24 25 public static IEnumerable<ApiScope> ApiScopes => 26 new ApiScope[] 27 { 28 new ApiScope("orderApiScope"), 29 new ApiScope("productApiScope"), 30 }; 31 32 public static IEnumerable<Client> Clients => 33 new Client[] 34 { 35 new Client 36 { 37 ClientId = "web client", 38 ClientName = "Web Client", 39 40 AllowedGrantTypes = GrantTypes.Code, 41 ClientSecrets = { new Secret("web client secret".Sha256()) }, 42 43 RedirectUris = { "http://localhost:5000/signin-oidc" }, 44 FrontChannelLogoutUri = "http://localhost:5000/signout-oidc", 45 PostLogoutRedirectUris = { "http://localhost:5000/signout-callback-oidc" }, 46 47 AllowedScopes = new [] { 48 IdentityServerConstants.StandardScopes.OpenId, 49 IdentityServerConstants.StandardScopes.Profile, 50 "orderApiScope", "productApiScope" 51 }, 52 AllowAccessTokensViaBrowser = true, 53 54 RequireConsent = true,//是否显示同意界面 55 AllowRememberConsent = false,//是否记住同意选项 56 } 57 }; 58}

Config中定义了2个api资源:orderApi,productApi。2个Scope:orderApiScope,productApiScope。1个客户端:web client,使用Code授权码模式,拥有openid,profile,orderApiScope,productApiScope 4个scope。

TestUsers类:

1public class TestUsers 2{ 3 public static List<TestUser> Users 4 { 5 get 6 { 7 var address = new 8 { 9 street_address = "One Hacker Way", 10 locality = "Heidelberg", 11 postal_code = 69118, 12 country = "Germany" 13 }; 14 15 return new List<TestUser> 16 { 17 new TestUser 18 { 19 SubjectId = "818727", 20 Username = "alice", 21 Password = "alice", 22 Claims = 23 { 24 new Claim(JwtClaimTypes.Name, "Alice Smith"), 25 new Claim(JwtClaimTypes.GivenName, "Alice"), 26 new Claim(JwtClaimTypes.FamilyName, "Smith"), 27 new Claim(JwtClaimTypes.Email, "AliceSmith@email.com"), 28 new Claim(JwtClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean), 29 new Claim(JwtClaimTypes.WebSite, "http://alice.com"), 30 new Claim(JwtClaimTypes.Address, JsonSerializer.Serialize(address), IdentityServerConstants.ClaimValueTypes.Json) 31 } 32 }, 33 new TestUser 34 { 35 SubjectId = "88421113", 36 Username = "bob", 37 Password = "bob", 38 Claims = 39 { 40 new Claim(JwtClaimTypes.Name, "Bob Smith"), 41 new Claim(JwtClaimTypes.GivenName, "Bob"), 42 new Claim(JwtClaimTypes.FamilyName, "Smith"), 43 new Claim(JwtClaimTypes.Email, "BobSmith@email.com"), 44 new Claim(JwtClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean), 45 new Claim(JwtClaimTypes.WebSite, "http://bob.com"), 46 new Claim(JwtClaimTypes.Address, JsonSerializer.Serialize(address), IdentityServerConstants.ClaimValueTypes.Json) 47 } 48 } 49 }; 50 } 51 } 52}

TestUsers没有做修改,用项目模板默认生成的就行。这里定义了2个用户alice,bob,密码与用户名相同。

至此,鉴权中心的代码修改就差不多了。这个项目也不放docker了,直接用vs来启动,让他运行在9080端口。/Properties/launchSettings.json修改一下:"applicationUrl": "http://localhost:9080"

Ocelot集成ids4

Ocelot保护api资源

鉴权中心搭建完成,下面整合到之前的Ocelot.APIGateway网关项目中。

首先NuGet安装IdentityServer4.AccessTokenValidation

image-20200706100658900

修改Startup:

1public void ConfigureServices(IServiceCollection services) 2{ 3 services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme) 4 .AddIdentityServerAuthentication("orderService", options => 5 { 6 options.Authority = "http://localhost:9080";//鉴权中心地址 7 options.ApiName = "orderApi"; 8 options.SupportedTokens = SupportedTokens.Both; 9 options.ApiSecret = "orderApi secret"; 10 options.RequireHttpsMetadata = false; 11 }) 12 .AddIdentityServerAuthentication("productService", options => 13 { 14 options.Authority = "http://localhost:9080";//鉴权中心地址 15 options.ApiName = "productApi"; 16 options.SupportedTokens = SupportedTokens.Both; 17 options.ApiSecret = "productApi secret"; 18 options.RequireHttpsMetadata = false; 19 }); 20 21 //添加ocelot服务 22 services.AddOcelot() 23 //添加consul支持 24 .AddConsul() 25 //添加缓存 26 .AddCacheManager(x => 27 { 28 x.WithDictionaryHandle(); 29 }) 30 //添加Polly 31 .AddPolly(); 32}

修改ocelot.json配置文件:

1{ 2 "DownstreamPathTemplate": "/products", 3 "DownstreamScheme": "http", 4 "UpstreamPathTemplate": "/products", 5 "UpstreamHttpMethod": [ "Get" ], 6 "ServiceName": "ProductService", 7 ...... 8 "AuthenticationOptions": { 9 "AuthenticationProviderKey": "productService", 10 "AllowScopes": [] 11 } 12}, 13{ 14 "DownstreamPathTemplate": "/orders", 15 "DownstreamScheme": "http", 16 "UpstreamPathTemplate": "/orders", 17 "UpstreamHttpMethod": [ "Get" ], 18 "ServiceName": "OrderService", 19 ...... 20 "AuthenticationOptions": { 21 "AuthenticationProviderKey": "orderService", 22 "AllowScopes": [] 23 } 24}

添加了AuthenticationOptions节点,AuthenticationProviderKey对应的是上面Startup中的定义。

Ocelot代理ids4

既然网关是客户端访问api的统一入口,那么同样可以作为鉴权中心的入口。使用Ocelot来做代理,这样客户端也无需知道鉴权中心的地址,同样修改ocelot.json:

1{ 2 "DownstreamPathTemplate": "/{url}", 3 "DownstreamScheme": "http", 4 "DownstreamHostAndPorts": [ 5 { 6 "Host": "localhost", 7 "Port": 9080 8 } 9 ], 10 "UpstreamPathTemplate": "/auth/{url}", 11 "UpstreamHttpMethod": [ 12 "Get", 13 "Post" 14 ], 15 "LoadBalancerOptions": { 16 "Type": "RoundRobin" 17 } 18}

添加一个鉴权中心的路由,实际中鉴权中心也可以部署多个实例,也可以集成Consul服务发现,实现方式跟前面章节讲的差不多,这里就不再赘述。

让网关服务运行在9070端口,/Properties/launchSettings.json修改一下:"applicationUrl": "http://localhost:9070"

客户端集成

首先NuGet安装Microsoft.AspNetCore.Authentication.OpenIdConnect

image-20200706121544645

修改Startup:

1public void ConfigureServices(IServiceCollection services) 2{ 3 services.AddAuthentication(options => 4 { 5 options.DefaultScheme = "Cookies"; 6 options.DefaultChallengeScheme = "oidc"; 7 }) 8 .AddCookie("Cookies") 9 .AddOpenIdConnect("oidc", options => 10 { 11 options.Authority = "http://localhost:9070/auth";//通过网关访问鉴权中心 12 //options.Authority = "http://localhost:9080"; 13 14 options.ClientId = "web client"; 15 options.ClientSecret = "web client secret"; 16 options.ResponseType = "code"; 17 18 options.RequireHttpsMetadata = false; 19 20 options.SaveTokens = true; 21 22 options.Scope.Add("orderApiScope"); 23 options.Scope.Add("productApiScope"); 24 }); 25 26 services.AddControllersWithViews(); 27 28 //注入IServiceHelper 29 //services.AddSingleton<IServiceHelper, ServiceHelper>(); 30 31 //注入IServiceHelper 32 services.AddSingleton<IServiceHelper, GatewayServiceHelper>(); 33} 34 35// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 36public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IServiceHelper serviceHelper) 37{ 38 if (env.IsDevelopment()) 39 { 40 app.UseDeveloperExceptionPage(); 41 } 42 else 43 { 44 app.UseExceptionHandler("/Home/Error"); 45 } 46 app.UseStaticFiles(); 47 48 app.UseRouting(); 49 50 app.UseAuthentication(); 51 52 app.UseAuthorization(); 53 54 app.UseEndpoints(endpoints => 55 { 56 endpoints.MapControllerRoute( 57 name: "default", 58 pattern: "{controller=Home}/{action=Index}/{id?}"); 59 }); 60 61 //程序启动时 获取服务列表 62 //serviceHelper.GetServices(); 63}

修改/Helper/IServiceHelper,方法定义增加accessToken参数:

1/// <summary> 2/// 获取产品数据 3/// </summary> 4/// <param name="accessToken"></param> 5/// <returns></returns> 6Task<string> GetProduct(string accessToken); 7 8/// <summary> 9/// 获取订单数据 10/// </summary> 11/// <param name="accessToken"></param> 12/// <returns></returns> 13Task<string> GetOrder(string accessToken);

修改/Helper/GatewayServiceHelper,访问接口时增加Authorization参数,传入accessToken:

1public async Task<string> GetOrder(string accessToken) 2{ 3 var Client = new RestClient("http://localhost:9070"); 4 var request = new RestRequest("/orders", Method.GET); 5 request.AddHeader("Authorization", "Bearer " + accessToken); 6 7 var response = await Client.ExecuteAsync(request); 8 if (response.StatusCode != HttpStatusCode.OK) 9 { 10 return response.StatusCode + " " + response.Content; 11 } 12 return response.Content; 13} 14 15public async Task<string> GetProduct(string accessToken) 16{ 17 var Client = new RestClient("http://localhost:9070"); 18 var request = new RestRequest("/products", Method.GET); 19 request.AddHeader("Authorization", "Bearer " + accessToken); 20 21 var response = await Client.ExecuteAsync(request); 22 if (response.StatusCode != HttpStatusCode.OK) 23 { 24 return response.StatusCode + " " + response.Content; 25 } 26 return response.Content; 27}

最后是/Controllers/HomeController的修改。添加Authorize标记:

1[Authorize] 2public class HomeController : Controller

修改Index action,获取accessToken并传入:

1public async Task<IActionResult> Index() 2{ 3 var accessToken = await HttpContext.GetTokenAsync("access_token"); 4 5 ViewBag.OrderData = await _serviceHelper.GetOrder(accessToken); 6 ViewBag.ProductData = await _serviceHelper.GetProduct(accessToken); 7 8 return View(); 9}

至此,客户端集成也已完成。

测试

为了方便,鉴权中心、网关、web客户端这3个项目都使用vs来启动,他们的端口分别是9080,9070,5000。之前的OrderAPI和ProductAPI还是在docker中不变。

为了让vs能同时启动多个项目,需要设置一下,解决方案右键属性:

image-20200706123144511

Ctor+F5启动项目。

3个项目都启动完成后,浏览器访问web客户端:http://localhost:5000/

image-20200706124027549

因为我还没登录,所以请求直接被重定向到了鉴权中心的登录界面。使用alice/alice这个账户登录系统。

image-20200706124523974

登录成功后,进入授权同意界面,你可以同意或者拒绝,还可以选择勾选scope权限。点击Yes,Allow按钮同意授权:

image-20200706124924213

同意授权后,就能正常访问客户端界面了。下面测试一下部分授权,这里没做登出功能,只能手动清理一下浏览器Cookie,ids4登出功能也很简单,可以自行百度。

image-20200706125257382

清除Cookie后,刷新页面又会转到ids4的登录界面,这次使用bob/bob登录:

image-20200706125759968

这次只勾选orderApiScope,点击Yes,Allow:

image-20200706130140730

这次客户端就只能访问订单服务了。当然也可以在鉴权中心去限制客户端的api权限,也可以在网关层面ocelot.json中限制,相信你已经知道该怎么做了。

总结

本文主要完成了IdentityServer4鉴权中心、Ocelot网关、web客户端之间的整合,实现了系统的统一授权认证。授权认证是几乎每个系统必备的功能,而IdentityServer4是.Net Core下优秀的授权认证方案。再次推荐一下B站@solenovex 杨老师的视频,地址:https://www.bilibili.com/video/BV16b411k7yM ,虽然视频有点老了,但还是非常受用。

需要代码的点这里:https://github.com/xiajingren/NetCoreMicroserviceDemo

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

swap空间的增减方法

(1)增大swap空间去激活swap交换区:swapoff v /dev/vg00/lvswap扩展交换lv:lvextend L 10G /dev/vg00/lvswap重新生成swap交换区:mkswap /dev/vg00/lvswap激活新生成的交换区:swapon v /dev/vg00/lvswap

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )