ASP.NET Core微服务之基于IdentityServer建立授权与验证服务(2)

本文涉及的产品
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
简介: _Tip:_ 此篇已加入.NET Core微服务基础系列文章索引上一篇我们基于IdentityServer4建立了一个AuthorizationServer,并且继承了QuickStartUI,能够成功获取Token了。

_Tip:_ 此篇已加入.NET Core微服务基础系列文章索引

上一篇我们基于IdentityServer4建立了一个AuthorizationServer,并且继承了QuickStartUI,能够成功获取Token了。这一篇我们了解下如何集成API Service和MVC Web Application。

一、集成API Service

1.1 添加ASP.NET Core WebAPI项目

  新建两个WebAPI程序,假设这里取名为ApiService01(占用端口5010)和ApiService02(占用端口5020)。

  为了方便快速启动,可以继续在launchSettings.json中删掉关于IISExpress的部分,由于是WebAPI,所以也不需要启动浏览器(将其设为false):

{
  "profiles": {
    "Manulife.DNC.MSAD.IdentityServer4Test.ApiService01": {
      "commandName": "Project",
      "launchBrowser": false,
      "launchUrl": "api/values",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      },
      "applicationUrl": "http://localhost:5010/"
    }
  }
}

1.2 安装IdentityServer4.AccessTokenValidation

NuGet>Install-Package IdentityServer4.AccessTokenValidation  

  安装完成之后,需要做一些适配,所以我们来注册一下关于其的配置:这里设置的默认模式是Bearer,其中AddIdentityServerAuthentication方法是将我们上一篇创建的授权服务器注册为token的处理人,即在本API程序中涉及到token的处理,都会移交给指定服务器(这里即上一篇中创建的AuthorizationServer)进行处理。

    public void ConfigureServices(IServiceCollection services)
    {
        ......

        // IdentityServer
        services.AddMvcCore().AddAuthorization().AddJsonFormatters();
        services.AddAuthentication(Configuration["Identity:Scheme"])
            .AddIdentityServerAuthentication(options =>
            {
                options.RequireHttpsMetadata = false; // for dev env
                options.Authority = $"http://{Configuration["Identity:IP"]}:{Configuration["Identity:Port"]}";
                options.ApiName = Configuration["Service:Name"]; // match with configuration in IdentityServer
            });

        // Swagger
        ......
    }

  配置文件中的内容如下所示:_这里需要注意的是Name需要和我们在上一篇中的InMemoryConfiguration中自行hard-code的一样_。

{
  "Service": {
    "Name": "clientservice",
    "Port": "5010",
    "DocName": "clientservice",
    "Version": "v1",
    "Title": "CAS Client Service API",
    "Description": "CAS Client Service API provide some API to help you get client information from CAS",
    "Contact": {
      "Name": "CAS 2.0 Team",
      "Email": "EdisonZhou@manulife.com"
    },
    "XmlFile": "Manulife.DNC.MSAD.IdentityServer4Test.ApiService01.xml"
  },
  "Identity": {
    "IP": "localhost",
    "Port": "5000",
    "Scheme": "Bearer"
  }
}

  此外,还需要在Configure方法中配置Authentication中间件:_这里需要注意的就是需要放在UseMvc()之前调用_。

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        // authentication
        app.UseAuthentication();

        app.UseMvc();

        // swagger
        .......
    }

  以上是ApiService01的配置,ApiService02的配置类似,只是配置文件中的信息从clientservice改为了productservice。

1.3 为要进行验证授权的方法添加[Authorize]特性

  由于我们创建WebAPI时,默认有一个ValuesController,保留它,我们直接为这个Controller添加一个[Authorize]特性。

    [Authorize]
    [Route("api/[controller]")]
    public class ValuesController : Controller
    {
        ......
    }

  这样的话,刚刚注册的中间件就会在请求的过程中基于传递过来的token进行Authorization,如果没有token或者token是非法的,它就会告诉api的消费者这个请求时未授权的(HTTP StatusCode 401)

1.4 简单测试一下

  测试之前首先确保AuthorizationServer和两个ApiService都一起启动,可以在解决方案属性的启动选项中进行设置。

  (1)不带token的情况

  (2)带正确token的情况

  首先请求获取一下token:这里我们使用的grant_type是client_credentials,也可以使用password(需要输入用户名和密码)。

  带上这个token再去调用api service

  (3)带不正确的token的情况(这里简单改一下token的值)

  (4)用刚刚授予(clientservice)的token访问未经授权的productservice

  我们也可以通过在ValuesController中加上如下的一句,来获取Claims => 它是从已验证过的token中抽取的信息。如果我们将上面获取的token的grant_type设置为password的话,那我们会在Claims中获取到用户ID的信息。

二、集成MVC Web Application

2.1 添加ASP.NET Core MVC项目

  新建一个ASP.NET Core MVC项目,这里假设取名为MvcClient(占用端口5100)

  仍然修改launchSettings.json,删掉IISExpress部分,但仍然选择启动浏览器:

{
  "profiles": {
    "Manulife.DNC.MSAD.IdentityServer4Test.MvcClient": {
      "commandName": "Project",
      "launchBrowser": true,
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      },
      "applicationUrl": "http://localhost:5100/"
    }
  }
}

2.2 为指定方法添加[Authorize]特性

  这里我们在HomeController的About方法上面添加[Authorize]特性:

    [Authorize]
    public IActionResult About()
    {
        ViewData["Message"] = "Your application description page.";

        return View();
    }

  这时如果我们直接访问About,会抛异常,告知我们No authenticationScheme was specified......

  而我们想要的效果是当User第一次点击About,页面重定向到AuthorizationServer (localhost:5000),当用户登录验证授权之后,再重定向到该网站。此后一定时间范围内的第二次,第三次点击,都不再需要再重定向到AuthorizationServer,而是直接读取保存的token。

2.3 添加OpenID Connect Authentication

  这一部分主要集中于做Authentication(身份验证)而非Authorization(授权)。

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc();

        JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); // ensure not change any return Claims from Authorization Server
        services.AddAuthentication(options =>
        {
            options.DefaultScheme = "Cookies";
            options.DefaultChallengeScheme = "oidc"; // oidc => open ID connect
        })
        .AddCookie("Cookies")
        .AddOpenIdConnect("oidc", options =>
        {
            options.SignInScheme = "Cookies";
            options.Authority = $"http://{Configuration["Identity:IP"]}:{Configuration["Identity:Port"]}";
            options.RequireHttpsMetadata = false; // please use https in production env
            options.ClientId = "cas.mvc.client.implicit";
            options.ResponseType = "id_token token"; // allow to return access token
            options.SaveTokens = true;
        });
    }

  这里我们使用的是implicit这个flow(详细内容可以阅读ddrsql的IdentityServer4之Implicit(隐式许可)),它主要用于客户端应用程序(主要指基于javascript的应用),它允许客户端程序重定向到AuthorizationServer,然后带着token重定向回来。值得一提的是这里的ResponseType为"id_token token",表示既获取id_token也获取access_token。而SaveTokens设为true则表示要将从AuthorizationServer返回的token持久化在cookie之中,这样就不用每次都去请求token了。

  当然,也还得在Configure方法中,配置Authentication中间件:

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseBrowserLink();
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler("/Home/Error");
        }

        app.UseAuthentication();

        app.UseStaticFiles();

        app.UseMvc(routes =>
        {
            routes.MapRoute(
                name: "default",
                template: "{controller=Home}/{action=Index}/{id?}");
        });
    }

  *.位置仍然需要在UseMvc之前。

2.4 在AuthorizationServer添加这个MvcClient

   在InMemoryConfiguration类中修改GetClients方法:

    public static IEnumerable<Client> GetClients()
    {
        return new[]
        {
            new Client
            {
                ClientId = "client.api.service",
                ClientSecrets = new [] { new Secret("clientsecret".Sha256()) },
                AllowedGrantTypes = GrantTypes.ResourceOwnerPasswordAndClientCredentials,
                AllowedScopes = new [] { "clientservice" }
            },
            ......,
            new Client
            {
                ClientId = "cas.mvc.client.implicit",
                ClientName = "CAS MVC Web App Client",
                AllowedGrantTypes = GrantTypes.Implicit,
                RedirectUris = { $"http://{Configuration["Clients:MvcClient:IP"]}:{Configuration["Clients:MvcClient:Port"]}/signin-oidc" },
                PostLogoutRedirectUris = { $"http://{Configuration["Clients:MvcClient:IP"]}:{Configuration["Clients:MvcClient:Port"]}/signout-callback-oidc" },
                AllowedScopes = new [] {
                    IdentityServerConstants.StandardScopes.OpenId,
                    IdentityServerConstants.StandardScopes.Profile,
                    "agentservice", "clientservice", "productservice"
                },
                AllowAccessTokensViaBrowser = true // can return access_token to this client
            }
        };
    }

  这里的ClientId要和MvcClient中设置的一致。RedirectUris是指登录成功之后需要重定向的地址(这里这个位置在MvcClient中),而PostLogoutRedirectUris是指登出之后需要重定向的地址。和API Service Client的设置不同的就是在AllowedScopes中给它增加了OpenId和Profile,因为我们为MvcClient设定的是oidc而不是bearer的模式。最后为了使用这些OpenID Connect Scopes,需要设置这些Identity Resources:

    public static IEnumerable<IdentityResource> GetIdentityResources()
    {
        return new List<IdentityResource>
        {
            new IdentityResources.OpenId(),
            new IdentityResources.Profile(),
        };
    }

    public void ConfigureServices(IServiceCollection services)
    {
        .......

        services.AddIdentityServer()
            //.AddDeveloperSigningCredential()
            .AddSigningCredential(new X509Certificate2(Path.Combine(basePath,
                Configuration["Certificates:CerPath"]),
                Configuration["Certificates:Password"]))
            .AddInMemoryIdentityResources(InMemoryConfiguration.GetIdentityResources())
            .AddTestUsers(InMemoryConfiguration.GetUsers().ToList())
            .AddInMemoryClients(InMemoryConfiguration.GetClients())
            .AddInMemoryApiResources(InMemoryConfiguration.GetApiResources());
        ......
    }

  同时,为了演示方便,我们在MvcClient的About视图中添加几句:

@{
    ViewData["Title"] = "About";
}
<h2>@ViewData["Title"]</h2>
<h3>@ViewData["Message"]</h3>

@using Microsoft.AspNetCore.Authentication
<div>
    <strong>id_token</strong>
    <span>@await ViewContext.HttpContext.GetTokenAsync("id_token")</span>
</div>
<div>
    <strong>access_token</strong>
    <span>@await ViewContext.HttpContext.GetTokenAsync("access_token")</span>
</div>

<dl>
    @foreach (var claim in User.Claims)
    {
        <dt>@claim.Type</dt>
        <dd>@claim.Value</dd>
    }
</dl>

  这里我们将id_token, access_token(_只有拿到access_token,才可以在MvcClient中发起请求调用API Service,而具体可以访问哪些API Service是在InMemoryConfiguration中配置的AllowScopes里面的那些_)都显示出来,还会将Claims数据也显示出来(这里的Claims数据就是从AuthorizationServer返回的token里面payload部分的数据,关于payload请搜索JWT)

2.5 简单测试一下

  (1)未登陆的情况

  拿到access_token,可以去JWT.io上进行解析看看:比如关注payload部分,可以得到很多信息,比如过期时间为1小时(默认)

  (2)已登录的情况

  这里为了方便演示,新增一个Logout方法:这里需要确保同时登出MvcClient的Cookies和OpenId Connect(即到Identity Server去清除单点登录的Session)。

    public class HomeController: Controller
    {
        ......

        public async Task Logout()
        {
            await HttpContext.SignOutAsync("Cookies");
            await HttpContext.SignOutAsync("oidc");
        }

        ......
    }

  最后,关于access token的生命周期,可以阅读一下园友晓晨Master(李志强)的《IdentityServer4实战 - AccessToken 生命周期分析》,里面提到一个时间偏移的概念,需要了解一下。另外,如果是前后端分离的结构,也可以浏览一下ddrsql的《IdentityServer4之Implicit(隐式许可) —— oidc-client-js前后端分离》,里面介绍了oidc-client这个JS库的使用,以及如何支持跨域。

三、小结

  本篇基于上一篇搭建好的AuthorizationServer,通过集成API Service与MVC Web Application来演示他们如何与Authorization Server的交互,了解了两种不同的Scheme(Bearer和Implicit),最后补充了一些材料供扩展阅读。但是,IdentityServer的内容很多,我只是学习了一些我要掌握以做POC的部分,其他还有很多功能和知识点我没有学习,大家可以通过搜索园子里各种大神(eg.晓晨Master, solenovex等等等等)的文章进行学习。后面我会将IdentityServer与Ocelot进行集成,尝试在API网关处做统一验证与授权。最后,感谢参考资料的作者们,本篇主要基于参考资料的学习而成的笔记。另外,一些朋友十分想要一些基于生产环境的采坑经验以及更加实际的应用案例,对此我只能说声抱歉,我目前仍然处于学习与准备POC阶段,目的是为了在公司里推广以及给老板安利(虽然很难),还没有实际项目迁移到.NET Core微服务架构上去,但是如果不努力让公司迁移和应用,那像我司一样的传统行业的金融保险公司是不会主动升级的,还请各位谅解。

示例代码

  Click => https://github.com/EdisonChou/EDC.IdentityServer4.Demo

参考资料

identityserver4官方文档》_=> 重点关注那些流程图与术语_

ddrsql,《IdentityServer4之Implicit(隐式许可)

solenovex,《学习Identity Server4的预备知识

solenovex,《使用Identity Server 4建立Authorization Server (1)

solenovex,《使用Identity Server 4建立Authorization Server (2)

solenovex,《使用Identity Server 4建立Authorization Server (3)

晓晨Master,《IdentityServer4实战 - AccessToken 生命周期分析

目录
相关文章
|
11天前
|
数据可视化 网络协议 C#
C#/.NET/.NET Core优秀项目和框架2024年3月简报
公众号每月定期推广和分享的C#/.NET/.NET Core优秀项目和框架(每周至少会推荐两个优秀的项目和框架当然节假日除外),公众号推文中有项目和框架的介绍、功能特点、使用方式以及部分功能截图等(打不开或者打开GitHub很慢的同学可以优先查看公众号推文,文末一定会附带项目和框架源码地址)。注意:排名不分先后,都是十分优秀的开源项目和框架,每周定期更新分享(欢迎关注公众号:追逐时光者,第一时间获取每周精选分享资讯🔔)。
|
1月前
|
开发框架 人工智能 .NET
C#/.NET/.NET Core拾遗补漏合集(持续更新)
C#/.NET/.NET Core拾遗补漏合集(持续更新)
|
1月前
|
Windows
windows server 2019 安装NET Framework 3.5失败,提示:“安装一个或多个角色、角色服务或功能失败” 解决方案
windows server 2019 安装NET Framework 3.5失败,提示:“安装一个或多个角色、角色服务或功能失败” 解决方案
|
1月前
|
开发框架 中间件 .NET
C# .NET面试系列七:ASP.NET Core
## 第一部分:ASP.NET Core #### 1. 如何在 controller 中注入 service? 在.NET中,在ASP.NET Core应用程序中的Controller中注入服务通常使用<u>依赖注入(Dependency Injection)</u>来实现。以下是一些步骤,说明如何在Controller中注入服务: 1、创建服务 首先,确保你已经在应用程序中注册了服务。这通常在Startup.cs文件的ConfigureServices方法中完成。例如: ```c# services.AddScoped<IMyService, MyService>(); //
60 0
|
2月前
|
开发框架 前端开发 .NET
福利来袭,.NET Core开发5大案例,30w字PDF文档大放送!!!
为了便于大家查找,特将之前开发的.Net Core相关的五大案例整理成文,共计440页,32w字,免费提供给大家,文章底部有PDF下载链接。
32 1
福利来袭,.NET Core开发5大案例,30w字PDF文档大放送!!!
|
2月前
|
算法 BI API
C#/.NET/.NET Core优秀项目和框架2024年1月简报
C#/.NET/.NET Core优秀项目和框架2024年1月简报
|
11天前
|
API 数据库 开发者
构建高效可靠的微服务架构:后端开发的新范式
【4月更文挑战第8天】 随着现代软件开发的复杂性日益增加,传统的单体应用架构面临着可扩展性、维护性和敏捷性的挑战。为了解决这些问题,微服务架构应运而生,并迅速成为后端开发领域的一股清流。本文将深入探讨微服务架构的设计原则、实施策略及其带来的优势与挑战,为后端开发者提供一种全新视角,以实现更加灵活、高效和稳定的系统构建。
18 0
|
25天前
|
负载均衡 测试技术 持续交付
高效后端开发实践:构建可扩展的微服务架构
在当今快速发展的互联网时代,后端开发扮演着至关重要的角色。本文将重点探讨如何构建可扩展的微服务架构,以及在后端开发中提高效率的一些实践方法。通过合理的架构设计和技术选型,我们可以更好地应对日益复杂的业务需求,实现高效可靠的后端系统。
|
25天前
|
监控 持续交付 API
构建高效可扩展的微服务架构
在当今快速迭代和竞争激烈的软件市场中,构建一个高效、可扩展且易于维护的后端系统变得尤为重要。微服务架构作为一种流行的分布式系统设计方式,允许开发者将应用程序划分为一系列小型、自治的服务,每个服务负责执行特定的业务功能。本文将探讨如何利用现代技术栈搭建一个符合这些要求的微服务架构,并讨论其潜在的挑战与解决方案。我们将涵盖服务划分策略、容器化、服务发现、API网关、持续集成/持续部署(CI/CD)以及监控和日志管理等关键主题,以帮助读者构建出既可靠又灵活的后端系统。
|
10天前
|
Kubernetes 安全 Java
构建高效微服务架构:从理论到实践
【4月更文挑战第9天】 在当今快速迭代与竞争激烈的软件市场中,微服务架构以其灵活性、可扩展性及容错性,成为众多企业转型的首选。本文将深入探讨如何从零开始构建一个高效的微服务系统,覆盖从概念理解、设计原则、技术选型到部署维护的各个阶段。通过实际案例分析与最佳实践分享,旨在为后端工程师提供一套全面的微服务构建指南,帮助读者在面对复杂系统设计时能够做出明智的决策,并提升系统的可靠性与维护效率。