.Net Core微服务入门全纪录七——IdentityServer4-授权认证

Tips:本篇已加入系列文章阅读目录,可点击查看更多相关文章。

前言

上一篇【.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方法:

// 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","Bob Smith"),"Bob"),"BobSmith@email.com"),"http://bob.com"),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

image-20200706100658900

修改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","UpstreamPathTemplate": "/orders","ServiceName": "OrderService",......
  "AuthenticationOptions": {
    "AuthenticationProviderKey": "orderService","AllowScopes": []
  }
}

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

Ocelot代理ids4

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

{
  "DownstreamPathTemplate": "/{url}","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

image-20200706121544645

修改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","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能同时启动多个项目,需要设置一下,解决方案右键属性:

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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


在上文中,我介绍了事件驱动型架构的一种简单的实现,并演示了一个完整的事件派发、订阅和处理的流程。这种实现太简单了,百十行代码就展示了一个基本工作原理。然而,要将这样的解决方案运用到实际生产环境,还有很长的路要走。今天,我们就研究一下在事件处理器中,对象生命周期的管理问题。事实上,不仅仅是在事件处理器
上文已经介绍了Identity Service的实现过程。今天我们继续,实现一个简单的Weather API和一个基于Ocelot的API网关。 回顾 《Angular SPA基于Ocelot API网关与IdentityServer4的身份认证与授权(一)》 Weather API Weather
最近我为我自己的应用开发框架Apworks设计了一套案例应用程序,并以Apache 2.0开源,开源地址是:https://github.com/daxnet/apworks-examples,目的是为了让大家更为方便地学习和使用.NET Core、最新的前端开发框架Angular,以及Apwork
HAL(Hypertext Application Language,超文本应用语言)是一种RESTful API的数据格式风格,为RESTful API的设计提供了接口规范,同时也降低了客户端与服务端接口的耦合度。很多当今流行的RESTful API开发框架,包括Spring REST,也都默认支
在前面两篇文章中,我详细介绍了基本事件系统的实现,包括事件派发和订阅、通过事件处理器执行上下文来解决对象生命周期问题,以及一个基于RabbitMQ的事件总线的实现。接下来对于事件驱动型架构的讨论,就需要结合一个实际的架构案例来进行分析。在领域驱动设计的讨论范畴,CQRS架构本身就是事件驱动的,因此,
HAL,全称为Hypertext Application Language,它是一种简单的数据格式,它能以一种简单、统一的形式,在API中引入超链接特性,使得API的可发现性(discoverable)更强,并具有自描述的特点。使用了HAL的API会更容易地被第三方开源库所调用,并且使用起来也很方便
何时使用领域驱动设计?其实当你的应用程序架构设计是面向业务的时候,你已经开始使用领域驱动设计了。领域驱动设计既不是架构风格(Architecture Style),也不是架构模式(Architecture Pattern),它也不是一种软件开发方法论,所以,是否应该使用领域驱动设计,以及什么时候使用
《在ASP.NET Core中使用Apworks快速开发数据服务》一文中,我介绍了如何使用Apworks框架的数据服务来快速构建用于查询和管理数据模型的RESTful API,通过该文的介绍,你会看到,使用Apworks框架开发数据服务是何等简单快捷,提供的功能也非常多,比如对Hypermedia的
在上一讲中,我们已经完成了一个完整的案例,在这个案例中,我们可以通过Angular单页面应用(SPA)进行登录,然后通过后端的Ocelot API网关整合IdentityServer4完成身份认证。在本讲中,我们会讨论在当前这种架构的应用程序中,如何完成用户授权。 回顾 《Angular SPA基于
Keycloak是一个功能强大的开源身份和访问管理系统,提供了一整套解决方案,包括用户认证、单点登录(SSO)、身份联合、用户注册、用户管理、角色映射、多因素认证和访问控制等。它广泛应用于企业和云服务,可以简化和统一不同应用程序和服务的安全管理,支持自托管或云部署,适用于需要安全、灵活且易于扩展的用
3月7日,微软发布了Visual Studio 2017 RTM,与之一起发布的还有.NET Core Runtime 1.1.0以及.NET Core SDK 1.0.0,尽管这些并不是最新版,但也已经从preview版本升级到了正式版。所以,在安装Visual Studio 2017时如果启用了
在上文中,我介绍了如何在Ocelot中使用自定义的中间件来修改下游服务的response body。今天,我们再扩展一下设计,让我们自己设计的中间件变得更为通用,使其能够应用在不同的Route上。比如,我们可以设计一个通用的替换response body的中间件,然后将其应用在多个Route上。 O
不少关注我博客的朋友都知道我在2009年左右开发过一个名为Apworks的企业级应用程序开发框架,旨在为分布式企业系统软件开发提供面向领域驱动(DDD)的框架级别的解决方案,并对多种系统架构风格提供支持。这个框架的开发和维护我坚持了很久,一直到2015年,我都一直在不停地重构这个项目。目前这个项目在
好吧,这个题目我也想了很久,不知道如何用最简单的几个字来概括这篇文章,原本打算取名《Angular单页面应用基于Ocelot API网关与IdentityServer4ʺSP.NET Identity实现身份认证与授权》,然而如你所见,这样的名字实在是太长了。所以,我不得不缩写“单页面应用”几个字
在前面两篇文章中,我介绍了基于IdentityServer4的一个Identity Service的实现,并且实现了一个Weather API和基于Ocelot的API网关,然后实现了通过Ocelot API网关整合Identity Service做身份认证的API请求。今天,我们进入前端开发,设计
Ocelot是ASP.NET Core下的API网关的一种实现,在微服务架构领域发挥了非常重要的作用。本文不会从整个微服务架构的角度来介绍Ocelot,而是介绍一下最近在学习过程中遇到的一个问题,以及如何使用中间件(Middleware)来解决这样的问题。 问题描述 在上文中,我介绍了一种在Angu
在大数据处理和人工智能时代,数据工厂(Data Factory)无疑是一个非常重要的大数据处理平台。市面上也有成熟的相关产品,比如Azure Data Factory,不仅功能强大,而且依托微软的云计算平台Azure,为大数据处理提供了强大的计算能力,让大数据处理变得更为稳定高效。由于工作中我的项目
在上文中,我们讨论了事件处理器中对象生命周期的问题,在进入新的讨论之前,首先让我们总结一下,我们已经实现了哪些内容。下面的类图描述了我们已经实现的组件及其之间的关系,貌似系统已经变得越来越复杂了。其中绿色的部分就是上文中新实现的部分,包括一个简单的Event Store,一个事件处理器执行上下文的接
在之前《在ASP.NET Core中使用Apworks快速开发数据服务》一文的评论部分,.NET大神张善友为我提了个建议,可以使用Compile As a Service的Roslyn为语法解析提供支持。在此非常感激友哥给我的建议,也让我了解了一些Roslyn的知识。使用Roslyn的一个很大的好处
很长一段时间以来,我都在思考如何在ASP.NET Core的框架下,实现一套完整的事件驱动型架构。这个问题看上去有点大,其实主要目标是为了实现一个基于ASP.NET Core的微服务,它能够非常简单地订阅来自于某个渠道的事件消息,并对接收到的消息进行处理,于此同时,它还能够向该渠道发送事件消息,以便