第九节:IdentityServer4简介及客户端模式和用户密码模式的应用

一. IDS4简介

1. 什么是IDS4

  IdentityServer是基于OpenID Connect协议标准的身份认证和授权程序,它实现了OpenID 和 OAuth 2.0 协议。

2. 相关地址

 (1).官网:https://identityserver4.readthedocs.io/en/latest/

 (2).GitHub地址:https://github.com/IdentityServer

 (2).三方中文文档:http://www.identityserver.com.cn/Home/Detail/Zhengtizongshu

3. IDS4有哪些功能

 (1).身份认证服务(官方认证的OpenID Connect实现)

 (2).单点登录与注销(SSO)

 (3).访问受控的Api

4. IDS4的内部概念

(1).用户-User

  使用已注册的客户端(指在id4中已经注册)访问资源的人。

(2).客户端-Client (即第三方应用)

  客户端就是从identityserver请求令牌的软件(你可以理解为一个app即可),既可以通过身份认证令牌来验证识别用户身份,又可以通过授权令牌来访问服务端的资源。但是客户端首先必须在申请令牌前已经在identityserver服务中注册过。实际客户端不仅可以是Web应用程序,app或桌面应用程序(你就理解为pc端的软件即可),SPA,服务器进程等。

(3).资源-Resources

  资源就是你想用identityserver保护的东西,可以是用户的身份数据或者api资源。每一个资源都有一个唯一的名称,客户端使用这个唯一的名称来确定想访问哪一个资源(在访问之前,实际identityserver服务端已经配置好了哪个客户端可以访问哪个资源,所以你不必理解为客户端只要指定名称他们就可以随便访问任何一个资源)

(4).身份令牌(d_token jwt)

  一个身份令牌指的就是对认证过程的描述。它至少要标识某个用户(Called the sub aka subject claim)的主身份信息,和该用户的认证时间和认证方式。但是身份令牌可以包含额外的身份数据,具体开发者可以自行设定,但是一般情况为了确保数据传输的效率,开发者一般不做过多额外的设置,大家也可以根据使用场景自行决定。

(5).访问令牌(access_token oauth 2.0)

  访问令牌允许客户端访问某个 API 资源。客户端请求到访问令牌,然后使用这个令牌来访问 API资源。访问令牌包含了客户端和用户(如果有的话,这取决于业务是否需要,但通常不必要)的相关信息,API通过这些令牌信息来授予客户端的数据访问权限。

5. IDS4的几种模式

 (1).客户端模式:ClientCredentials

 (2).用户名密码模式:ResourceOwnerPassword

 (3).隐式模式(简化模式):implicit

 (4).授权码模式:Code

除此之外,还有多种模式共存,比如: 客户端+用户名密码模式  ResourceOwnerPasswordAndClientCredentials

 

二. 客户端模式

一. 模式深究

1. 含义

  指【客户端(也就是 第三方应用)】以自己的名义,而不是以用户的名义,向【服务提供商】的【认证服务器】进行认证。严格地说,客户端模式并不属于OAuth2.0 框架所要解决的问题。在这种模式中,【用户】直接向【客户端】注册,【客户端】以自己的名义要求【服务提供商】提供服务,其实不存在授权问题。

2. 运行流程

 

步骤:

A. 客户端(第三方应用)向认证服务器发送请求进行身份认证,并要求得到一个访问令牌Token。

B. 认证服务器确认无误后(认证),向客户端颁发访问令牌Token(授权)。

C. 客户端拿到访问令牌(token),向资源服务器发送请求,获取需要的信息。(上图中没有画)

二. 基于IDS4代码实操

1. 项目环境准备

 (1). MyClient1:客户端控制台程序。

 (2). PostMan工具:也充当客户端程序。

 (3). ID4Test:认证+授权服务器.

 (4). ProductService:资源服务器.

PS.几种授权模式都会产生token,这里我单纯的想使用一下token,那么我直接给业务服务器ProductService中的api接口增加校验,这个校验直接加在业务服务器上,仅仅是为了测试。(后面介绍加在Ocelot上)

2.搭建测试步骤

 (一). IDS4认证授权服务器的配置

 (1).通过Nuget给ID4Test安装【IdentityServer4 4.0.2】

 (2).新建Config1配置类,包括两个方法:GetApiResources 和 GetClients. 其中GetApiResources里包含需要保护的Api业务服务器名称,GetClients里包含了哪些客户端资源可以访问,其中可以通过AllowedScopes = { "GoodsService", "ProductService" } 来授权哪个客户端能访问哪些api资源,例外还要配置 ClientId、校验方式(GrantTypes.ClientCredentials)、密钥

 (3).Startup中的ConfigureService和Config的配置,如代码所示.

 (4).通过命令启动项目【dotnet ID4Test.dll --urls="http://*:7040" --ip="127.0.0.1" --port=7040 】

配置类:

    /// <summary>
    /// 客户端模式
    /// </summary>
    public class Config1
    {
        /// <summary>
        /// 配置Api范围集合
        /// 4.x版本新增的配置
        /// </summary>
        /// <returns></returns>
        public static IEnumerable<ApiScope> GetApiScopes()
        {
            return new List<ApiScope>
            {
                new ApiScope("GoodsService"),
                new ApiScope("OrderService"),
                new ApiScope("ProductService")
             };
        }


        /// <summary>
        /// 需要保护的Api资源
        /// 4.x版本新增后续Scopes的配置
        /// </summary>
        /// <returns></returns>
        public static IEnumerable<ApiResource> GetApiResources()
        {
            List<ApiResource> resources = new List<ApiResource>();
            //ApiResource第一个参数是ServiceName,第二个参数是描述
            resources.Add(new ApiResource("GoodsService", "GoodsService服务需要保护哦") { Scopes = { "GoodsService" } });
            resources.Add(new ApiResource("OrderService", "OrderService服务需要保护哦") { Scopes = { "OrderService" } });
            resources.Add(new ApiResource("ProductService", "ProductService服务需要保护哦") { Scopes = { "ProductService" } });
            return resources;
        }


        /// <summary>
        /// 可以使用ID4 Server 客户端资源
        /// </summary>
        /// <returns></returns>
        public static IEnumerable<Client> GetClients()
        {
            List<Client> clients = new List<Client>() {
                new Client
                {
                    ClientId = "client1",//客户端ID                             
                    AllowedGrantTypes = GrantTypes.ClientCredentials, //验证类型:客户端验证
                    ClientSecrets ={ new Secret("0001".Sha256())},    //密钥和加密方式
                    AllowedScopes = { "GoodsService", "OrderService", "ProductService" }        //允许访问的api服务
                },
                new Client
                {
                    ClientId = "client2",//客户端ID                             
                    AllowedGrantTypes = GrantTypes.ClientCredentials, //验证类型:客户端验证
                    ClientSecrets ={ new Secret("0002".Sha256())},    //密钥和加密方式
                    AllowedScopes = { "GoodsService" , "ProductService" } //允许访问的api服务
                },
                 new Client
                {
                    ClientId = "client3",//客户端ID                             
                    AllowedGrantTypes = GrantTypes.ClientCredentials, //验证类型:客户端验证
                    ClientSecrets ={ new Secret("0003".Sha256())},    //密钥和加密方式
                    AllowedScopes = {"OrderService" }        //允许访问的api服务
                },
                new Client
                {
                    ClientId = "client4",//客户端ID                             
                    AllowedGrantTypes = GrantTypes.ClientCredentials, //验证类型:客户端验证
                    ClientSecrets ={ new Secret("0004".Sha256())},    //密钥和加密方式
                    AllowedScopes = { "ProductService" },        //允许访问的api服务
                    //基于角色授权
                   Claims=
                    {
                        new ClientClaim("role","ypfRole")
                    },
                    ClientClaimsPrefix="", //把前缀设置成空,就IDS4和Core MVC之间就不用转换了
                }
            };
            return clients;
        }
    }
View Code

Startup类:

  public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            //1. 客户端模式
            services.AddIdentityServer()
                  .AddDeveloperSigningCredential()    //生成Token签名需要的公钥和私钥,存储在bin下tempkey.rsa(生产场景要用真实证书,此处改为AddSigningCredential)
                  .AddInMemoryApiResources(Config1.GetApiResources())  //存储需要保护api资源
                  .AddInMemoryApiScopes(Config1.GetApiScopes())        //配置api范围 4.x版本必须配置的
                  .AddInMemoryClients(Config1.GetClients()); //存储客户端模式(即哪些客户端可以用)

            services.AddControllers();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseRouting();
            app.UseAuthorization();

            //1.启用IdentityServe4
            app.UseIdentityServer();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }
View Code

(二). ProductService资源服务器

 (1).通过Nuget给ProductService安装 【IdentityServer4.AccessTokenValidation 3.0.1】

 (2).在ConfigureService通过AddIdentityServerAuthentication连接ID4服务器,进行校验。这里ApiName中的“ProductService”必须是ID4中GetApiResources中添加的。

特别注意:这个Authority要用127.0.0.1, 不用Localhost,因为我们获取token的时候,使用的地址也是127.0.0.1,必须对应起来.

 (3).在Config中添加认证中间件 app.UseAuthentication();

 (4).新建一个GetMsg接口,并加上特性[Authorize]。

 (5).配置命令行ip+port启动,通过指令【dotnet ProductService.dll --urls="http://*:7007" --ip="127.0.0.1" --port=7007】启动

控制器代码:

 [Route("api/[controller]/[action]")]
    [ApiController]
    public class HomeController : ControllerBase
    {

        /// <summary>
        /// 资源服务器的api
        /// </summary>
        /// <returns></returns>
        [Authorize]
        [HttpGet]
        public string GetMsg()
        {
            //快速获取token的方式
            string token = HttpContext.GetTokenAsync("access_token").Result;

            return $"ypf";
        }


    }
View Code

Startup类:

 public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {

            //校验AccessToken,从身份校验中心(ID4Test)进行校验
            services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)  //Bear模式
                    .AddIdentityServerAuthentication(options =>
                    {
                        options.Authority = "http://127.0.0.1:7040"; // 1、授权中心地址
                        options.ApiName = "ProductService"; // 2、api名称(项目具体名称)
                        options.RequireHttpsMetadata = false; // 3、https元数据,不需要
                    });

            services.AddControllers();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            app.UseRouting();


            //认证中间件(服务于上ID4校验,一定要放在UseAuthorization之前)
            app.UseAuthentication();

            app.UseAuthorization();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }
View Code

 (三).用PostMan充当客户端测试

场景1.

 用PostMan直接发送Get请求,请求地址为:http://127.0.0.1:7007/api/Home/GetMsg ,测试结果:报401未授权

场景2.

 (1).用PostMan发送Post请求,http://127.0.0.1:7040/connect/token 获取token (这里用客户端模式进行测试),表单(form-data)参数如下:

  client_id=client1

  grant_type=client_credentials

  client_secret=0001

 请求成功,返回token值和过期时间。

注:/connect/token 是IDS4内部封装地址

 (2).用PostMan继续发送Get请求,请求地址为:http://127.0.0.1:7007/api/Home/GetMsg, 其中Header中要配置 token:Bear xxxxxxxx(B中获取的token值),(也可用PostMan中Authorization选项卡下,TYPE选择Bearer Token,然后内容直接输入B中获取的token即可)

 (3).测试结果:请求成功,返回“ypf”.

运行结果:

 (四).用控制台充当客户端测试

 (1).首先通过Nuget安装【IdentityModel 4.3.1】,可以扩展HttpClient请求

 (2).利用GetDiscoveryDocumentAsync方法可以获取更多的服务地址,比如 disco.TokenEndpoint 即/connect/token

 (3).携带 ClientId、ClientSecret,利用RequestClientCredentialsTokenAsync方法,向认证服务器发送请求,获取token

 (4).客户端拿到token利用SetBearerToken方法,可以设置 token:Bear xxxxxxxx的格式,向资源服务器发送请求.

 (5).请求成功,获取结果.

PS:这里是为了演示,使用HttpClient,正式环境建议使用HttpClientFactory

控制台代码:

             //认证服务器地址
            string rzAddress = "http://127.0.0.1:7040";
            //资源服务器api地址
            string resAddress = "http://127.0.0.1:7007/api/Home/GetMsg";
            //资源服务器api地址(基于角色授权)
            string resAddress2 = "http://127.0.0.1:7007/api/Home/GetMsg2";

            #region 一.客户端模式
            {
                var client = new HttpClient();
                var disco = await client.GetDiscoveryDocumentAsync(rzAddress);
                if (disco.IsError)
                {
                    Console.WriteLine(disco.Error);
                    return;
                }
                //向认证服务器发送请求,要求获得令牌
                Console.WriteLine("---------------------------- 一.向认证服务器发送请求,要求获得令牌-----------------------------------");
                var tokenResponse = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
                {
                    //在上面的地址上拼接:/connect/token,最终:http://127.0.0.1:7040/connect/token
                    Address = disco.TokenEndpoint,
                    ClientId = "client1",
                    ClientSecret = "0001",
                    //空格分隔的请求范围列表,省略的话是默认配置的所有api资源,如: client1对应的是:{ "GoodsService", "OrderService", "ProductService" }  
                    //这里填写的范围可以和配置的相同或者比配置的少,比如{ "GoodsService OrderService"},这里只是一个范围列表,并不是请求哪个api资源必须要  写在里面
                    //但是如果配置的和默认配置出现不同,则认证不能通过 比如{ "ProductService OrderService111"},
                    //综上所述:此处可以不必配置
                    //Scope = "ProductService OrderService111"
                });
                if (tokenResponse.IsError)
                {
                    Console.WriteLine($"认证错误:{tokenResponse.Error}");
                    Console.ReadKey();
                }
                Console.WriteLine(tokenResponse.Json);

                //携带token向资源服务器发送请求
                Console.WriteLine("----------------------------二.携带token向资源服务器发送请求-----------------------------------");
                var apiClient = new HttpClient();
                apiClient.SetBearerToken(tokenResponse.AccessToken);   //设置Token格式  【Bear xxxxxx】
                var response = await apiClient.GetAsync(resAddress);
                if (!response.IsSuccessStatusCode)
                {
                    Console.WriteLine(response.StatusCode);
                    Console.ReadKey();
                }
                else
                {
                    var content = await response.Content.ReadAsStringAsync();
                    Console.WriteLine($"请求资源服务器的结果为:{content}");
                }
            }
            #endregion
View Code

运行结果:

3. 补充基于角色授权

 在上述基础上,增加客户端client4, 并配置角色授权 new Claim("role","ypfRole"),这里清空一下前缀哦,新增GetMsg2接口,添加[Authorize(Roles = "ypfRole")] ,角色授权用客户端进行测试,只有client4才能访问GetMsg2接口。

控制器代码:

    [Route("api/[controller]/[action]")]
    [ApiController]
    public class HomeController : ControllerBase
    {

        [Authorize(Roles = "ypfRole")]  //角色授权
        [HttpGet]
        public string GetMsg2()
        {
            return "ypf2";
        }
    }
View Code

客户端控制台代码:

            //认证服务器地址
            string rzAddress = "http://127.0.0.1:7040";
            //资源服务器api地址(基于角色授权)
            string resAddress2 = "http://127.0.0.1:7007/api/Home/GetMsg2";        

 {
                var client = new HttpClient();
                var disco = await client.GetDiscoveryDocumentAsync(rzAddress);
                if (disco.IsError)
                {
                    Console.WriteLine(disco.Error);
                    Console.ReadKey();
                }
                //向认证服务器发送请求,要求获得令牌
                Console.WriteLine("---------------------------- 一.向认证服务器发送请求,要求获得令牌-----------------------------------");
                var tokenResponse = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
                {
                    //在上面的地址上拼接:/connect/token,最终:http://127.0.0.1:7040/connect/token
                    Address = disco.TokenEndpoint,
                    ClientId = "client4",
                    ClientSecret = "0004",
                    //空格分隔的请求范围列表,省略的话是默认配置的所有api资源,如: client1对应的是:{ "GoodsService", "OrderService", "ProductService" }  
                    //这里填写的范围可以和配置的相同或者比配置的少,比如{ "GoodsService OrderService"},这里只是一个范围列表,并不是请求哪个api资源必须要  写在里面
                    //但是如果配置的和默认配置出现不同,则认证不能通过 比如{ "ProductService OrderService111"},
                    //综上所述:此处可以不必配置
                    //Scope = "ProductService OrderService111"
                });
                if (tokenResponse.IsError)
                {
                    Console.WriteLine($"认证错误:{tokenResponse.Error}");
                    Console.ReadKey();
                }
                Console.WriteLine(tokenResponse.Json);

                //携带token向资源服务器发送请求
                Console.WriteLine("----------------------------二.携带token向资源服务器发送请求-----------------------------------");
                var apiClient = new HttpClient();
                apiClient.SetBearerToken(tokenResponse.AccessToken);   //设置Token格式  【Bear xxxxxx】
                var response = await apiClient.GetAsync(resAddress2);
                if (!response.IsSuccessStatusCode)
                {
                    Console.WriteLine($"请求报错:{response.StatusCode}");
                    Console.ReadKey();
                }
                else
                {
                    var content = await response.Content.ReadAsStringAsync();
                    Console.WriteLine($"请求资源服务器的结果为:{content}");
                }
            }
View Code

运行结果:

 

三. 用户名密码模式

一. 模式深究

(1). 含义:

  用户向客户端提供自己的用户名和密码。客户端使用这些信息,向"服务商提供商"索要授权。

  在这种模式中,用户必须把自己的账号和密码给客户端,但是客户端不得储存密码。这通常用在用户对客户端高度信任的情况下,比如客户端是操作系统的一部分,或者由一个著名公司出品。而认证服务器只有在其他授权模式无法执行的情况下,才能考虑使用这种模式。

(2). 运行流程:

步骤:

A. 用户向客户端提供用户名和密码。

B. 客户端将用户名和密码发给认证服务器,并要求得到一个访问令牌Token。

C. 认证服务器确认无误后(认证),向客户端颁发访问令牌Token(授权)。

D. 客户端拿到访问令牌(token),向资源服务器发送请求,获取需要的信息。(上图中没有画)

二. 基于IDS4代码实操

1.项目环境准备

 (1). MyClient1:客户端控制台程序。

 (2). PostMan工具:也充当客户端程序。

 (3). ID4Test:认证+授权服务器.

 (4). ProductService:资源服务器.

PS.几种授权模式都会产生token,现在我单纯的想使用一下token,那么我直接给业务服务器ProductService中的api接口增加校验,这个校验直接加在业务服务器上,仅仅是为了测试。(后面介绍加在Ocelot上)。

2.搭建测试步骤

 (一). IDS4认证授权服务器的配置

 (1).通过Nuget给ID4Test安装【IdentityServer4 4.0.2】

 (2).新建Config2配置类,包括两个方法:GetApiResources 、 GetClients、GetUsers. 其中GetApiResources里包含需要保护的Api业务服务器名称,GetClients里包含了哪些客户端资源可以访问,其中可以通过AllowedScopes = { "GoodsService", "OrderService" } 来授权哪个客户端能访问哪些api资源,例外还要配置 ClientId、校验方式(GrantTypes.ResourceOwnerPassword)、密钥。GetUsers里包含了哪些用户名和密码可以访问

 (3).Startup中的ConfigureService和Config的配置,如代码所示. 多了AddTestUsers(Config2.GetUsers().ToList())

 (4).通过命令启动项目【dotnet ID4Test.dll --urls="http://*:7040" --ip="127.0.0.1" --port=7040 】

配置类:

  /// <summary>
    /// 用户名密码模式
    /// </summary>
    public class Config2
    {
        /// <summary>
        /// 配置Api范围集合
        /// 4.x版本新增的配置
        /// </summary>
        /// <returns></returns>
        public static IEnumerable<ApiScope> GetApiScopes()
        {
            return new List<ApiScope>
            {
                new ApiScope("GoodsService"),
                new ApiScope("OrderService"),
                new ApiScope("ProductService")
             };
        }


        /// <summary>
        /// 需要保护的Api资源
        /// 4.x版本新增后续Scopes的配置
        /// </summary>
        /// <returns></returns>
        public static IEnumerable<ApiResource> GetApiResources()
        {
            List<ApiResource> resources = new List<ApiResource>();
            //ApiResource第一个参数是ServiceName,第二个参数是描述
            resources.Add(new ApiResource("GoodsService", "GoodsService服务需要保护哦") { Scopes = { "GoodsService" } });
            resources.Add(new ApiResource("OrderService", "OrderService服务需要保护哦") { Scopes = { "OrderService" } });
            resources.Add(new ApiResource("ProductService", "ProductService服务需要保护哦") { Scopes = { "ProductService" } });
            return resources;
        }


        /// <summary>
        /// 可以使用ID4 Server 客户端资源
        /// </summary>
        /// <returns></returns>
        public static IEnumerable<Client> GetClients()
        {
            List<Client> clients = new List<Client>() {
                new Client
                {
                    ClientId = "client1",//客户端ID                             
                    AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, //验证类型:客户端验证
                    ClientSecrets ={ new Secret("0001".Sha256())},    //密钥和加密方式
                    AllowedScopes = { "GoodsService", "OrderService", "ProductService" }        //允许访问的api服务
                },
                new Client
                {
                    ClientId = "client2",//客户端ID                             
                    AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, //验证类型:客户端验证
                    ClientSecrets ={ new Secret("0002".Sha256())},    //密钥和加密方式
                    AllowedScopes = { "GoodsService", "ProductService" }        //允许访问的api服务
                },
                 new Client
                {
                    ClientId = "client3",//客户端ID                             
                    AllowedGrantTypes = GrantTypes.ResourceOwnerPasswordAndClientCredentials, //验证类型:用户名密码模式 和 客户端模式
                    ClientSecrets ={ new Secret("0003".Sha256())},    //密钥和加密方式
                    AllowedScopes = {"OrderService" }        //允许访问的api服务
                }
            };
            return clients;
        }

        /// <summary>
        /// 定义可以使用ID4的用户资源
        /// </summary>
        /// <returns></returns>
        public static IEnumerable<TestUser> GetUsers()
        {
            return new List<TestUser>()
            {
                new TestUser
                {
                    SubjectId = "10001",
                    Username = "ypf1",     //账号
                    Password = "ypf001"    //密码
                },
                new TestUser
                {
                    SubjectId = "10002",
                    Username = "ypf2",
                    Password = "ypf002"
                },
                new TestUser
                {
                    SubjectId = "10003",
                    Username = "ypf3",
                    Password = "ypf003"
                }
            };
        }
    }
View Code

Startup类:

 public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            //2. 用户名密码模式
            services.AddIdentityServer()
                  .AddDeveloperSigningCredential()    //生成Token签名需要的公钥和私钥,存储在bin下tempkey.rsa(生产场景要用真实证书,此处改为AddSigningCredential)
                  .AddInMemoryApiResources(Config2.GetApiResources())  //存储需要保护api资源
                  .AddInMemoryClients(Config2.GetClients()) //存储客户端模式(即哪些客户端可以用)
                  .AddInMemoryApiScopes(Config1.GetApiScopes())        //配置api范围 4.x版本必须配置的
                  .AddTestUsers(Config2.GetUsers().ToList());  //存储哪些用户、密码可以访问 (用户名密码模式)

            services.AddControllers();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseRouting();
            app.UseAuthorization();

            //1.启用IdentityServe4
            app.UseIdentityServer();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }
View Code

(二). ProductService资源服务器

 (1).通过Nuget给ProductService安装 【IdentityServer4.AccessTokenValidation 3.0.1】

 (2).在ConfigureService通过AddIdentityServerAuthentication连接ID4服务器,进行校验。这里ApiName中的“ProductService”必须是ID4中GetApiResources中添加的。

特别注意:这个Authority要用127.0.0.1, 不用Localhost,因为我们获取token的时候,使用的地址也是127.0.0.1,必须对应起来。

 (3).在Config中添加认证中间件 app.UseAuthentication();

 (4).新建一个GetMsg接口,并加上特性[Authorize]。

 (5).配置命令行ip+port启动,通过指令【dotnet ProductService.dll --urls="http://*:7007" --ip="127.0.0.1" --port=7007】启动

代码同客户端模式

 (三).用PostMan充当客户端测试

 场景1.

  用PostMan直接发送Get请求,请求地址为:http://127.0.0.1:7007/api/Home/GetMsg ,测试结果:报401未授权

 场景2.

  (1).用PostMan发送Post请求,http://127.0.0.1:7040/connect/token,用Post请求,表单提交(form-data)的方式进行测试,表单参数如下:

    client_id=client1

    grant_type=password (此处和上面的不一样哦)

    client_secret=0001

    username=ypf1

    password=ypf001

  请求成功,返回token值和过期时间。 client_id和client_secret也可以改为 client2和0002,如果不正确,则检验不通过。

注:/connect/token 是ID4内部封装地址。

 (2).用PostMan继续发送Get请求,请求地址为:http://127.0.0.1:7007/api/Home/GetMsg, 其中Header中要配置 token:Bear xxxxxxxx(B中获取的token值),(也可用PostMan中Authorization选项卡下,TYPE选择Bearer Token,然后内容直接输入B中获取的token即可)

 (3).测试结果:请求成功,返回“ypf”.

运行结果:

 

(四).用控制台充当客户端测试

 (1).首先通过Nuget安装【IdentityModel 4.3.1】,可以扩展HttpClient请求

 (2).利用GetDiscoveryDocumentAsync方法可以获取更多的服务地址,比如 disco.TokenEndpoint 即/connect/token

 (3).携带 ClientId、ClientSecret、UserName、Password,利用RequestPasswordTokenAsync方法,向认证服务器发送请求,获取token

 (4).客户端拿到token利用SetBearerToken方法,可以设置 token:Bear xxxxxxxx的格式,向资源服务器发送请求.

 (5).请求成功,获取结果.

PS:这里是为了演示,使用HttpClient,正式环境建议使用HttpClientFactory。

客户端代码:

           //认证服务器地址
            string rzAddress = "http://127.0.0.1:7040";
            //资源服务器api地址
            string resAddress = "http://127.0.0.1:7007/api/Home/GetMsg";
 {
                var client = new HttpClient();
                var disco = await client.GetDiscoveryDocumentAsync(rzAddress);
                if (disco.IsError)
                {
                    Console.WriteLine(disco.Error);
                    Console.ReadKey();
                }
                //向认证服务器发送请求,要求获得令牌
                Console.WriteLine("---------------------------- 一.向认证服务器发送请求,要求获得令牌-----------------------------------");
                var tokenResponse = await client.RequestPasswordTokenAsync(new PasswordTokenRequest
                {
                    Address = disco.TokenEndpoint,
                    ClientId = "client1",
                    ClientSecret = "0001",
                    UserName = "ypf2",
                    Password = "ypf002"
                    //Scope = ""   //可以不用配置
                });
                if (tokenResponse.IsError)
                {
                    Console.WriteLine($"认证错误:{tokenResponse.Error}");
                    Console.ReadKey();
                }
                Console.WriteLine(tokenResponse.Json);
                //携带token向资源服务器发送请求
                Console.WriteLine("----------------------------二.携带token向资源服务器发送请求-----------------------------------");
                var apiClient = new HttpClient();
                apiClient.SetBearerToken(tokenResponse.AccessToken);    //设置Token格式  【Bear xxxxxx】
                var response = await apiClient.GetAsync(resAddress);
                if (!response.IsSuccessStatusCode)
                {
                    Console.WriteLine(response.StatusCode);
                    Console.ReadKey();
                }
                else
                {
                    var content = await response.Content.ReadAsStringAsync();
                    Console.WriteLine($"请求资源服务器的结果为:{content}");
                }
            }
View Code

运行结果:

 

 

 

 

 

 

 

 

!

  • 作       者 : Yaopengfei(姚鹏飞)
  • 博客地址 : http://www.cnblogs.com/yaopengfei/
  • 声     明1 : 如有错误,欢迎讨论,请勿谩骂^_^。
  • 声     明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。
 

 

posted @ 2020-06-17 17:11  Yaopengfei  阅读(2666)  评论(7编辑  收藏  举报