zoukankan      html  css  js  c++  java
  • IdentityServer4系列 | 客户端凭证模式

    一、前言

    从上一篇关于 快速搭建简易项目中,通过手动或者官方模板的方式简易的实现了我们的IdentityServer授权服务器搭建,并做了相应的配置和UI配置,实现了获取Token方式。

    而其中我们也注意到了三点就是,有哪些用户(users)可以通过哪些客户端(clents)来访问我们的哪些API保护资源 (API)。

    所以在这一篇中,我们将通过多种授权模式中的客户端凭证模式进行说明,主要针对介绍IdentityServer保护API的资源,客户端认证授权访问API资源。

    二、初识

    Client Credentials 客户端凭证模式:客户端(Client)请求授权服务器验证,通过验证就发access token,Client直接以已自己的名义去访问Resource server的一些受保护资源。

    用户使用这个令牌访问资源服务器,当令牌失效时使用刷新令牌去换取新的令牌(刷新令牌有效时间大于访问令牌,刷新令牌的功能不做详细介绍)

    这种方式给出的令牌,是针对第三方应用的,而不是针对用户的,即有可能多个用户共享同一个令牌。

    2.1 适用范围

    这种模式一般只用在服务端与服务端之间的认证

    适用于没有前端的命令行应用,即在命令行请求令牌
    

    认证服务器不提供像用户数据这样的重要资源,仅仅是有限的只读资源或者一些开放的API。例如使用了第三方的静态文件服务,如Google Storage或Amazon S3。这样,你的应用需要通过外部API调用并以应用本身而不是单个用户的身份来读取或修改这些资源。这样的场景就很适合使用客户端证书授权。

    2.2 Client Credentials流程:

     +---------+                                  +---------------+
     |         |                                  |               |
     |         |>--(A)- Client Authentication --->| Authorization |
     | Client  |                                  |     Server    |
     |         |<--(B)---- Access Token ---------<|               |
     |         |                                  |               |
     +---------+                                  +---------------+
    

    客户端凭据许可流程描述

    (A)客户端与授权服务器进行身份验证并向令牌端点请求访问令牌。
    (B)授权服务器对客户端进行身份验证,如果有效,颁发访问令牌。

    2.2.1 过程详解


    访问令牌请求
    参数 是否必须 含义
    grant_type 必需 授权类型,值固定为“client_credentials”。
    scope 可选 表示授权范围。

    示例:
    客户端身份验证两种方式
    1、Authorization: Bearer base64(resourcesServer:123)
    2、client_id(客户端标识),client_secret(客户端秘钥)。

    POST /token HTTP/1.1
    Host: authorization-server.com
     
    grant_type=client_credentials
    &client_id=xxxxxxxxxx
    &client_secret=xxxxxxxxxx
    

    2.2.2 访问令牌响应

    刷新令牌不应该包含在内。
    HTTP/1.1 200 OK
    Content-Type: application/json;charset=UTF-8
    Cache-Control: no-store
    Pragma: no-cache
    
    {
    "access_token":"2YotnFZFEjr1zCsicMWpAA",
    "token_type":"Bearer",
    "expires_in":3600,
    "scope":"server"
    }
    

    三、实践

    在示例实践中,我们将创建一个授权访问服务,定义一个API和要访问它的客户端,客户端通过IdentityServer上请求访问令牌,并使用它来访问API。

    3.1 搭建 Authorization Server 服务

    搭建认证授权服务

    3.1.1 安装Nuget包

    IdentityServer4 程序包

    3.1.2 配置内容

    建立配置内容文件Config.cs

    public static class Config
    {
        public static IEnumerable<IdentityResource> IdentityResources =>
            new IdentityResource[]
        {
            new IdentityResources.OpenId(),
            new IdentityResources.Profile(),
        };
    
    
        public static IEnumerable<ApiScope> ApiScopes =>
            new ApiScope[]
        {
            new ApiScope("client_scope1")
        };
    
        public static IEnumerable<ApiResource> ApiResources =>
            new ApiResource[]
        {
            new ApiResource("api1","api1")
            {
                Scopes={"client_scope1" }
            }
        };
    
        public static IEnumerable<Client> Clients =>
            new Client[]
        {
            // m2m client credentials flow client
            new Client
            {
                ClientId = "credentials_client",
                ClientName = "Client Credentials Client",
    
                AllowedGrantTypes = GrantTypes.ClientCredentials,
                ClientSecrets = { new Secret("511536EF-F270-4058-80CA-1C89C192F69A".Sha256()) 			},
    
                AllowedScopes = { "client_scope1" }
            },
        };
    }
    

    3.1.3 注册服务

    在startup.cs中ConfigureServices方法添加如下代码:

            public void ConfigureServices(IServiceCollection services)
            {
                var builder = services.AddIdentityServer();
                  // .AddTestUsers(TestUsers.Users);
    
                // in-memory, code config
                builder.AddInMemoryIdentityResources(Config.IdentityResources);
                builder.AddInMemoryApiScopes(Config.ApiScopes);
                builder.AddInMemoryApiResources(Config.ApiResources);
                builder.AddInMemoryClients(Config.Clients);
    
                // not recommended for production - you need to store your key material somewhere secure
                builder.AddDeveloperSigningCredential();
            }
    

    3.1.4 配置管道

    在startup.cs中Configure方法添加如下代码:

            public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
            {
                if (env.IsDevelopment())
                {
                    app.UseDeveloperExceptionPage();
                }
    
                app.UseRouting();
                app.UseIdentityServer();
                app.UseEndpoints(endpoints =>
                {
                    endpoints.MapGet("/", async context =>
                    {
                        await context.Response.WriteAsync("Hello World!");
                    });
                });
            }
    

    以上内容是快速搭建简易IdentityServer项目服务的方式,具体说明可以看上一篇的内容。

    3.2 搭建API资源

    实现对API资源进行保护

    3.2.1 快速搭建一个API项目

    3.2.2 安装Nuget包

    IdentityServer4.AccessTokenValidation 包

    3.2.3 注册服务

    在startup.cs中ConfigureServices方法添加如下代码:

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllersWithViews();
            services.AddAuthorization();
    
            services.AddAuthentication("Bearer")
              .AddIdentityServerAuthentication(options =>
              {
                  options.Authority = "http://localhost:5001";
                  options.RequireHttpsMetadata = false;
                  options.ApiName = "api1";
              });
        }
    

    AddAuthentication把Bearer配置成默认模式,将身份认证服务添加到DI中。

    AddIdentityServerAuthentication把IdentityServer的access token添加到DI中,供身份认证服务使用。

    3.2.4 配置管道

    在startup.cs中Configure方法添加如下代码:

            public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
            {
                if (env.IsDevelopment())
                {
                    app.UseDeveloperExceptionPage();
                }    
                app.UseRouting();
                app.UseAuthentication();
                app.UseAuthorization();
                app.UseEndpoints(endpoints =>
                {
                    endpoints.MapDefaultControllerRoute();
                });
            }
    

    UseAuthentication将身份验证中间件添加到管道中;

    UseAuthorization 将启动授权中间件添加到管道中,以便在每次调用主机时执行身份验证授权功能。

    2.5 添加API资源接口

    [Route("api/[Controller]")]
    [ApiController]
    public class IdentityController:ControllerBase
    {
        [HttpGet("getUserClaims")]
        [Authorize]
        public IActionResult GetUserClaims()
        {
            return new JsonResult(from c in User.Claims select new { c.Type, c.Value });
        }
    }
    

    在IdentityController 控制器中添加 [Authorize] , 在进行请求资源的时候,需进行认证授权通过后,才能进行访问。

    3.3 搭建Client客户端

    实现对API资源的访问和获取资源

    3.3.1 搭建一个窗体程序

    3.3.2 安装Nuget包

    IdentityModel

    3.3.3 获取令牌

    客户端与授权服务器进行身份验证并向令牌端点请求访问令牌。授权服务器对客户端进行身份验证,如果有效,颁发访问令牌。

    IdentityModel 包括用于发现 IdentityServer 各个终结点(EndPoint)的客户端库。

    我们可以使用从 IdentityServer 元数据获取到的Token终结点请求令牌:

            private void getToken_Click(object sender, EventArgs e)
            {
                var client = new HttpClient();
                var disco = client.GetDiscoveryDocumentAsync(this.txtIdentityServer.Text).Result;
                if (disco.IsError)
                {
                    this.tokenList.Text = disco.Error;
                    return;
                }
                //请求token
                tokenResponse = client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
                {
                    Address = disco.TokenEndpoint,
                    ClientId =this.txtClientId.Text,
                    ClientSecret = this.txtClientSecret.Text,
                    Scope = this.txtApiScopes.Text
                }).Result;
    
                if (tokenResponse.IsError)
                {
                    this.tokenList.Text = disco.Error;
                    return;
                }
                this.tokenList.Text = JsonConvert.SerializeObject(tokenResponse.Json);
                this.txtToken.Text = tokenResponse.AccessToken;
            }
    

    3.3.4 调用API

    要将Token发送到API,通常使用HTTP Authorization标头。 这是使用SetBearerToken扩展方法完成。

        private void getApi_Click(object sender, EventArgs e)
        {
            //调用认证api
            if (string.IsNullOrEmpty(txtToken.Text))
            {
                MessageBox.Show("token值不能为空");
                return;
            }
            var apiClient = new HttpClient();
            //apiClient.SetBearerToken(tokenResponse.AccessToken);
            apiClient.SetBearerToken(this.txtToken.Text);
    
            var response = apiClient.GetAsync(this.txtApi.Text).Result;
            if (!response.IsSuccessStatusCode)
            {
                this.resourceList.Text = response.StatusCode.ToString();
            }
            else
            {
                this.resourceList.Text = response.Content.ReadAsStringAsync().Result;
            }
    
        }
    

    以上展示的代码有不明白的,可以看本篇项目源码,项目地址为 :

    3.4 效果

    3.4.1 项目测试

    3.4.2 postman测试

    四、问题

    注意,如果你的代码没问题,但是依然报错,比如“无效的scope”,“Audience validation failed”等问题。

    在3.1.x 到 4.x 的变更中,ApiResourceScope 正式独立出来为 ApiScope 对象,区别ApiResourceScope的关系, Scope 是属于ApiResource 的一个属性,可以包含多个Scope

    所以在配置ApiResource、ApiScope、Clients中,我们有些地方需要注意:

    在3.x版本中

     public static IEnumerable<ApiResource> GetApiResources()
     {
         return new[] { new ApiResource("api1", "api1") };
     }
    

    改成4.x版本为

    public static IEnumerable<ApiResource> ApiResources =>
        new ApiResource[]
    {
        new ApiResource("api1","api1")
        {
            Scopes={"client_scope1" }
        }
    };
    
    public static IEnumerable<ApiScope> ApiScopes =>
        new ApiScope[]
    {
        new ApiScope("client_scope1")
    };
    

    因此,

    这里比之前3.x版本多了一个添加ApiScopes的方法:

    builder.AddInMemoryApiScopes(Config.ApiScopes);
    

    因为接下来有要保护的API资源,所以需要添加一行:

    builder.AddInMemoryApiResources(Config.ApiResources);
    
    1. 如果在4.x版本中,不添加ApiScopes方法的话,在获取token令牌的时候一直“无效的scope”等错误
    2. 在授权访问保护资源的时候,如果ApiResource中不添加Scopes, 会一直报Audience validation failed错误,得到401错误,所以在4.x版本中写法要不同于3.x版本

    所以,需要注意的是4.x版本的ApiScope和ApiResource是分开配置的,然后在ApiResource中一定要添加Scopes。

    五、总结

    1. 本篇主要以客户端凭证模式进行授权,我们通过创建一个认证授权访问服务,定义一个API和要访问它的客户端,客户端通过IdentityServer上请求访问令牌,并使用它来控制访问API。
    2. 在文中可能出现的问题,我们通过查找解决,以及前后版本之间的差异,并总结说明问题。
    3. 在后续会对其中的其他授权模式,数据库持久化问题,以及如何应用在API资源服务器中和配置在客户端中,会进一步说明。
    4. 如果有不对的或不理解的地方,希望大家可以多多指正,提出问题,一起讨论,不断学习,共同进步。
    5. 项目地址

    六、附加

    Client Authentication认证

    client-credentials资料

  • 相关阅读:
    迁移到MSYS2 与 Qt 工具链注意的几个事情(g++在链接时,符号依赖项查找遵循从左至右的顺序,但qmake会自动合并造成错误。使用脚本给Mingw32-make创造出一个局部的VC编译环境)
    MSYS2 瘦身小攻略(使用junction)
    Boost.Http
    MySQL中临时表的基本创建与使用教程(CREATETEMPORARY TABLE)
    Javascript设计模式
    CORS
    axure & Markman
    JSON序列化那点事儿
    软件开发生涯
    CoffeeScript NgComponent
  • 原文地址:https://www.cnblogs.com/i3yuan/p/13882134.html
Copyright © 2011-2022 走看看