前言

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

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

鉴权中心

创建ids4项目

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

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

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

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

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

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

鉴权中心配置

修改Startup的ConfigureServices方法:

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

Config类:

public static class Config
{
public static IEnumerable<IdentityResource> IdentityResources =>
new IdentityResource[]
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
}; public static IEnumerable<ApiResource> ApiResources =>
new ApiResource[]
{
new ApiResource("orderApi","订单服务")
{
ApiSecrets ={ new Secret("orderApi secret".Sha256()) },
Scopes = { "orderApiScope" }
},
new ApiResource("productApi","产品服务")
{
ApiSecrets ={ new Secret("productApi secret".Sha256()) },
Scopes = { "productApiScope" }
}
}; public static IEnumerable<ApiScope> ApiScopes =>
new ApiScope[]
{
new ApiScope("orderApiScope"),
new ApiScope("productApiScope"),
}; public static IEnumerable<Client> Clients =>
new Client[]
{
new Client
{
ClientId = "web client",
ClientName = "Web Client", AllowedGrantTypes = GrantTypes.Code,
ClientSecrets = { new Secret("web client secret".Sha256()) }, RedirectUris = { "http://localhost:5000/signin-oidc" },
FrontChannelLogoutUri = "http://localhost:5000/signout-oidc",
PostLogoutRedirectUris = { "http://localhost:5000/signout-callback-oidc" }, AllowedScopes = new [] {
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"orderApiScope", "productApiScope"
},
AllowAccessTokensViaBrowser = true, RequireConsent = true,//是否显示同意界面
AllowRememberConsent = false,//是否记住同意选项
}
};
}

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

TestUsers类:

public class TestUsers
{
public static List<TestUser> Users
{
get
{
var address = new
{
street_address = "One Hacker Way",
locality = "Heidelberg",
postal_code = 69118,
country = "Germany"
}; return new List<TestUser>
{
new TestUser
{
SubjectId = "818727",
Username = "alice",
Password = "alice",
Claims =
{
new Claim(JwtClaimTypes.Name, "Alice Smith"),
new Claim(JwtClaimTypes.GivenName, "Alice"),
new Claim(JwtClaimTypes.FamilyName, "Smith"),
new Claim(JwtClaimTypes.Email, "AliceSmith@email.com"),
new Claim(JwtClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean),
new Claim(JwtClaimTypes.WebSite, "http://alice.com"),
new Claim(JwtClaimTypes.Address, JsonSerializer.Serialize(address), IdentityServerConstants.ClaimValueTypes.Json)
}
},
new TestUser
{
SubjectId = "88421113",
Username = "bob",
Password = "bob",
Claims =
{
new Claim(JwtClaimTypes.Name, "Bob Smith"),
new Claim(JwtClaimTypes.GivenName, "Bob"),
new Claim(JwtClaimTypes.FamilyName, "Smith"),
new Claim(JwtClaimTypes.Email, "BobSmith@email.com"),
new Claim(JwtClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean),
new Claim(JwtClaimTypes.WebSite, "http://bob.com"),
new Claim(JwtClaimTypes.Address, JsonSerializer.Serialize(address), IdentityServerConstants.ClaimValueTypes.Json)
}
}
};
}
}
}

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

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

Ocelot集成ids4

Ocelot保护api资源

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

首先NuGet安装IdentityServer4.AccessTokenValidation

修改Startup:

public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication("orderService", options =>
{
options.Authority = "http://localhost:9080";//鉴权中心地址
options.ApiName = "orderApi";
options.SupportedTokens = SupportedTokens.Both;
options.ApiSecret = "orderApi secret";
options.RequireHttpsMetadata = false;
})
.AddIdentityServerAuthentication("productService", options =>
{
options.Authority = "http://localhost:9080";//鉴权中心地址
options.ApiName = "productApi";
options.SupportedTokens = SupportedTokens.Both;
options.ApiSecret = "productApi secret";
options.RequireHttpsMetadata = false;
}); //添加ocelot服务
services.AddOcelot()
//添加consul支持
.AddConsul()
//添加缓存
.AddCacheManager(x =>
{
x.WithDictionaryHandle();
})
//添加Polly
.AddPolly();
}

修改ocelot.json配置文件:

{
"DownstreamPathTemplate": "/products",
"DownstreamScheme": "http",
"UpstreamPathTemplate": "/products",
"UpstreamHttpMethod": [ "Get" ],
"ServiceName": "ProductService",
......
"AuthenticationOptions": {
"AuthenticationProviderKey": "productService",
"AllowScopes": []
}
},
{
"DownstreamPathTemplate": "/orders",
"DownstreamScheme": "http",
"UpstreamPathTemplate": "/orders",
"UpstreamHttpMethod": [ "Get" ],
"ServiceName": "OrderService",
......
"AuthenticationOptions": {
"AuthenticationProviderKey": "orderService",
"AllowScopes": []
}
}

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

Ocelot代理ids4

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

{
"DownstreamPathTemplate": "/{url}",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 9080
}
],
"UpstreamPathTemplate": "/auth/{url}",
"UpstreamHttpMethod": [
"Get",
"Post"
],
"LoadBalancerOptions": {
"Type": "RoundRobin"
}
}

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

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

客户端集成

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

修改Startup:

public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
options.Authority = "http://localhost:9070/auth";//通过网关访问鉴权中心
//options.Authority = "http://localhost:9080"; options.ClientId = "web client";
options.ClientSecret = "web client secret";
options.ResponseType = "code"; options.RequireHttpsMetadata = false; options.SaveTokens = true; options.Scope.Add("orderApiScope");
options.Scope.Add("productApiScope");
}); services.AddControllersWithViews(); //注入IServiceHelper
//services.AddSingleton<IServiceHelper, ServiceHelper>(); //注入IServiceHelper
services.AddSingleton<IServiceHelper, GatewayServiceHelper>();
} // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IServiceHelper serviceHelper)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles(); app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
}); //程序启动时 获取服务列表
//serviceHelper.GetServices();
}

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

/// <summary>
/// 获取产品数据
/// </summary>
/// <param name="accessToken"></param>
/// <returns></returns>
Task<string> GetProduct(string accessToken); /// <summary>
/// 获取订单数据
/// </summary>
/// <param name="accessToken"></param>
/// <returns></returns>
Task<string> GetOrder(string accessToken);

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

public async Task<string> GetOrder(string accessToken)
{
var Client = new RestClient("http://localhost:9070");
var request = new RestRequest("/orders", Method.GET);
request.AddHeader("Authorization", "Bearer " + accessToken); var response = await Client.ExecuteAsync(request);
if (response.StatusCode != HttpStatusCode.OK)
{
return response.StatusCode + " " + response.Content;
}
return response.Content;
} public async Task<string> GetProduct(string accessToken)
{
var Client = new RestClient("http://localhost:9070");
var request = new RestRequest("/products", Method.GET);
request.AddHeader("Authorization", "Bearer " + accessToken); var response = await Client.ExecuteAsync(request);
if (response.StatusCode != HttpStatusCode.OK)
{
return response.StatusCode + " " + response.Content;
}
return response.Content;
}

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

[Authorize]
public class HomeController : Controller

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

public async Task<IActionResult> Index()
{
var accessToken = await HttpContext.GetTokenAsync("access_token"); ViewBag.OrderData = await _serviceHelper.GetOrder(accessToken);
ViewBag.ProductData = await _serviceHelper.GetProduct(accessToken); return View();
}

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

测试

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

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

Ctor+F5启动项目。

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

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

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

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

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

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

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

总结

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

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

.Net Core微服务入门全纪录(七)——IdentityServer4-授权认证的更多相关文章

  1. .Net Core微服务入门全纪录(八)——Docker Compose与容器网络

    Tips:本篇已加入系列文章阅读目录,可点击查看更多相关文章. 前言 上一篇[.Net Core微服务入门全纪录(七)--IdentityServer4-授权认证]中使用IdentityServer4 ...

  2. .Net Core微服务入门全纪录(二)——Consul-服务注册与发现(上)

    前言 上一篇[.Net Core微服务入门全纪录(一)--项目搭建]讲到要做到服务的灵活伸缩,那么需要有一种机制来实现它,这个机制就是服务注册与发现.当然这也并不是必要的,如果你的服务实例很少,并且很 ...

  3. .Net Core微服务入门全纪录(三)——Consul-服务注册与发现(下)

    前言 上一篇[.Net Core微服务入门全纪录(二)--Consul-服务注册与发现(上)]已经成功将我们的服务注册到Consul中,接下来就该客户端通过Consul去做服务发现了. 服务发现 同样 ...

  4. .Net Core微服务入门全纪录(四)——Ocelot-API网关(上)

    前言 上一篇[.Net Core微服务入门全纪录(三)--Consul-服务注册与发现(下)]已经使用Consul完成了服务的注册与发现,实际中光有服务注册与发现往往是不够的,我们需要一个统一的入口来 ...

  5. .Net Core微服务入门全纪录(五)——Ocelot-API网关(下)

    前言 上一篇[.Net Core微服务入门全纪录(四)--Ocelot-API网关(上)]已经完成了Ocelot网关的基本搭建,实现了服务入口的统一.当然,这只是API网关的一个最基本功能,它的进阶功 ...

  6. .Net Core微服务入门全纪录(六)——EventBus-事件总线

    前言 上一篇[.Net Core微服务入门全纪录(五)--Ocelot-API网关(下)]中已经完成了Ocelot + Consul的搭建,这一篇简单说一下EventBus. EventBus-事件总 ...

  7. .Net Core微服务入门全纪录(完结)——Ocelot与Swagger

    Tips:本篇已加入系列文章阅读目录,可点击查看更多相关文章. 前言 上一篇[.Net Core微服务入门全纪录(八)--Docker Compose与容器网络]完成了docker-compose.y ...

  8. .Net Core微服务入门全纪录(一)——项目搭建

    前言 写这篇博客主要目的是记录一下自己的学习过程,只能是简单入门级别的,因为水平有限就写到哪算哪吧,写的不对之处欢迎指正. 什么是微服务? 关于微服务的概念解释网上有很多... 个人理解,微服务是一种 ...

  9. 【NET CORE微服务一条龙应用】第三章 认证授权与动态权限配置

    介绍 系列目录:[NET CORE微服务一条龙应用]开始篇与目录 在微服务的应用中,统一的认证授权是必不可少的组件,本文将介绍微服务中网关和子服务如何使用统一的权限认证 主要介绍内容为: 1.子服务如 ...

随机推荐

  1. java实现第五届蓝桥杯生物芯片

    生物芯片 X博士正在研究一种生物芯片,其逻辑密集度.容量都远远高于普通的半导体芯片. 博士在芯片中设计了 n 个微型光源,每个光源操作一次就会改变其状态,即:点亮转为关闭,或关闭转为点亮. 这些光源的 ...

  2. Linux 工作管理

    把进程放入后台 进程后加&,这时,进程在后台是运行的 tar -zcf etc.tar.gz /etc & 在执行程序时,快速按下ctrl+z快捷键,这时,进程在后台是停止的 查看后台 ...

  3. wtf.sh-150

    前言: 最近在刷ctf题,在理解了大佬们的wp后自己做完之后,从头到尾把思路过了一遍,做次记录为以后复习和打比赛做准备,同时也希望能帮到别人. 0x01 进入网址,发现是一个论坛,有注册,登录和发表的 ...

  4. 01.Markdown学习

    Markdown学习 一.标题 在想要设置为标题的文字前面加#来表示(#后面有空格) 一个#是一级标题,二个#是二级标题,以此类推.支持六级标题. 示例: # 这是一级标题 ## 这是二级标题 ### ...

  5. 第一个SpringMVC程序 (配置版)

    通过配置版本的MVC程序,可以了解到MVC的底层原理,实际开发我们用的是注解版的! 1.新建一个普通Maven的项目,然后添加web的支持 2.导入相关的SpringMVC的依赖 3.配置web.xm ...

  6. electron-vue报错:Webpack ReferenceError: process is not defined

    electron-vue报错:Webpack ReferenceError: process is not defined 博客说明 文章所涉及的资料来自互联网整理和个人总结,意在于个人学习和经验汇总 ...

  7. c++ cc24a_demo //转换函数,用来做转换操作符,int()括号里面必须是空的,必须定义为const,代码示范

    c++ cc24a_demo //转换函数,用来做转换操作符,int()括号里面必须是空的,必须定义为const,代码示范 #include <iostream> #include < ...

  8. C++_继承

    C++支持单继承和多继承,并提供3类继承:public.private及protected.在public继承中,每个派生类对象都是基类对象,反之基类对象却不是派生类对象.派生类成员无法直接访问基本的 ...

  9. IDEA+Maven+Tomcat构建Web项目的三种方法

    [本文版权归微信公众号"代码艺术"(ID:onblog)所有,若是转载请务必保留本段原创声明,违者必究.若是文章有不足之处,欢迎关注微信公众号私信与我进行交流!] 本文将介绍三种方 ...

  10. talonspilder的的提问

    有个问题:我的代码是: [1]summary=TextField(css_select="#intro>p") [2]def tal_summary(self,summary ...