zoukankan      html  css  js  c++  java
  • 【One by One系列】IdentityServer4(二)使用客户端凭证(Client Credentials)保护API资源

    书接上回,我们将会正式开始介绍IdentityServer4

    IdentityServer4是实现了OAuth2.0+OpenId Connect两种协议的优秀第三方库,属于.net生态中的优秀成员。可以很容易集成至ASP.NET Core,颁发token。

    使用Id4基本步骤如下

    • 1.Startup.Configure方法中调用
    app.UseIdentityServer();
    

    添加中间件,把Id4添加至http请求处理管道,这使得Id4可以为OpenID Connect和OAuth2协议描述的端点(如/connect/token)请求提供服务。

    • 2.Startup.ConfigureServices中注册IdentityServer4
     services.AddIdentityServer(options=>
                                {
                                    ...
                                });
    
    • 3.配置Identity Server
      • 用于签名的凭据(credentials)
      • 用户可能会请求访问的Identity资源和API资源
        • Identity资源表示提供给客户端进行用户识别的信息(声明)。 声明可能包括用户名称、电子邮件地址等。
        • API资源表示用户可通过访问令牌访问的受保护数据或功能。 API 资源的一个示例是要求授权的 Web API(或 API集合)。
      • 会请求获取token的客户端
      • 用户信息的存储机制,如ASP.NET Core Identity或者其他机制

    当你指明Id4使用的客户端和资源,可以将IEnumerable<T>传递给接受内存中的客户端或资源存储的方法,如果在更复杂的场景,可以通过依赖注入的方式提供客户端和资源提供程序类型。

    IdentityServer4 使用自定义 IClientStore 类型提供的内存中资源和客户端的示例配置:

    public IServiceProvider ConfigureServices(IServiceCollection services)
    {
        //...
        services.AddSingleton<IClientStore, CustomClientStore>();
        services.AddIdentityServer()
            .AddSigningCredential("CN=sts")
            .AddInMemoryApiResources(MyApiResourceProvider.GetAllResources())
            .AddAspNetIdentity<ApplicationUser>();
        //...
    }
    

    经过上面的概述,我们先易后难,将从最简单的客户端凭证开始实战搭建IdentityServer4项目并以此保护api资源,首先客户端凭证属于OAuth2.0的一种授权方式。

    • 主要是向IdentityServer发送post请求token?grant_type=client_credentials& client_id=CLIENT_ID& client_secret=CLIENT_SECRET,获取access-token,以此来访问api资源。
    • 在IdentityServer4中,增加了Scope参数,表明了客户端的访问权限

    1.安装Id4模板

    dotnet new -i IdentityServer4.Templates
    

    • AdminUI:测试,生产环境需要交钱,商业软件
    • ASP.NET Core Identity:结合ASP.NET Core Indentity
    • Empty:空模板
    • Entity Frame Store:使用ef数据持久化身份认证信息
    • In-Memory Stores and Test Users:添加内存中的用户认证信息,和测试用户
    • Quickstart UI (UI assets only):UI

    2.创建ASP.NET Core应用,搭载Id4

    2.1 创建项目

    • 使用IdentityServer4的空模板创建应用
    md quickstart
    cd quickstart
    
    md src
    cd src
    
    #空模板 项目
    dotnet new is4empty -n IdentityServer
    The template "IdentityServer4 Empty" was created successfully.
    
    • 项目添加解决方案
    cd ..
    dotnet new sln -n QuickStart
    
    dotnet sln add .IdentityServerIdentityServer.csproj
    

    2.2 修改launchSettings.json

    • 测试环境,使用http,删掉IIS相关的配置
    {
      "profiles": {
        "SelfHost": {
          "commandName": "Project",
          "launchBrowser": true,
          "launchUrl": ".well-known/openid-configuration",
          "environmentVariables": {
            "ASPNETCORE_ENVIRONMENT": "Development"
          },
          "applicationUrl": "http://localhost:5001/"
        }
      }
    }
    

    2.3 定义一个api scope

    上篇与前文都介绍过,scope代表资源所有者在被保护资源那里的一些权限,可以把被保护资源分为不同的scope,具体的粒度由开发自定义。

    • 模板中ApiScope为空,在Config.cs增加
    public static IEnumerable<ApiScope> ApiScopes =>
    new ApiScope[]
    {
        //新增
        new ApiScope("api1", "My API")
    };
    
    • 第二个参数,是displayname

    2.4 定义一个客户端

    要让我们的IdentityServer给客户端颁发token,就要让客户端在IdentityServer注册。

    客户端,模板中的客户端与scope一样为空,在Config.cs增加客户端,代码如下:

     public static IEnumerable<Client> Clients =>
                new Client[]
                {
                     new Client
                        {
                            ClientId = "client app",
    
                            // no interactive user, use the clientid/secret for authentication
                            AllowedGrantTypes = GrantTypes.ClientCredentials,
    
                            // secret for authentication
                            ClientSecrets =
                            {
                                new Secret("secret-123456".Sha256())
                            },
    
                            // scopes that client has access to
                            AllowedScopes = { "api1" }
                        }
                };
    

    我们之后会增加这个定义的客户端,这个客户端将会访问AllowedScopes指定的api scope。

    注意:在此场景下,客户端跟用户是没有交互的,身份认证是通过IdentityServer的客户密钥。

    官方描述:你可以把ClientId和ClientSecret看作应用程序本身的登录名和密码。它向身份服务器表明您的应用程序的身份(我是xx应用程序,想访问服务器)。

    2.5 注册IdentityServer

    注释模板代码Startup.ConfigureServices()所有代码,增加代码:加载定义的资源和客户端,代码如下:

    public void ConfigureServices(IServiceCollection services)
    {
        var builder = services.AddIdentityServer()
            .AddInMemoryApiScopes(Config.ApiScopes)
            .AddInMemoryClients(Config.Clients);
    
        // omitted for brevity
    }
    

    配置完成。运行并浏览器访问http://localhost:5001/.well-known/openid-configuration,就能看到discovery document.

    • 它是IdentityServer中的标准端点
    • 客户端和APIs会使用它下载必要的配置数据,容后再表

    在第一次启动时,IdentityServer将创建一个开发者签名密钥,它是一个名为tempkey.rsa的文件。您不必将该文件签入源代码版本控制,如果不存在该文件,它将被重新创建。

    3.创建webapi

    限制开始创建我们需要保护的api资源

    3.1 新建项目

    dotnet new webapi -n webapi
    cd ..
    dotnet sln add  .webapiwebapi.csproj
    

    3.2 修改launchSettings.json

    {
      "profiles": {
        "Api": {
          "commandName": "Project",
          "launchBrowser": true,
          "applicationUrl": "http://localhost:6001",
          "environmentVariables": {
            "ASPNETCORE_ENVIRONMENT": "Development"
          }
        }
      }
    }
    

    3.3 新增api

    IdentityController.cs

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

    3.4 引入nuget包

    Microsoft.AspNetCore.Authentication.JwtBearer

    这个包是当收到请求时,对授权头中JWT的具体身份认证

    dotnet add .webapiwebapi.csproj package Microsoft.AspNetCore.Authentication.JwtBearer
    

    3.5 注册服务和添加中间件

    最后一步是将身份认证服务添加到依赖注入中,并将身份认证中间件添加到管道中。以便:

    • 验证传入的token,确保token来自可信的颁布者(服务器)
    • 验证这个token在这个api中使用是有效的(也就是受众)

    看代码:

    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();
    
            services.AddAuthentication("Bearer")
                .AddJwtBearer("Bearer", options =>
                {
                    //token颁发者
                    options.Authority = "http://localhost:5001";
    
                    options.TokenValidationParameters = new TokenValidationParameters
                    {
                        ValidateAudience = false
                    };
                    options.RequireHttpsMetadata = false;
                });
            
            services.AddAuthorization(options =>
                {
                    options.AddPolicy("ApiScope", policy =>
                    {
                        policy.RequireAuthenticatedUser();
                        
                        //scope
                        policy.RequireClaim("scope", "api1");
                    });
                });
        }
    
        public void Configure(IApplicationBuilder app)
        {
            app.UseRouting();
    
            app.UseAuthentication();
            app.UseAuthorization();
    
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers().RequireAuthorization("ApiScope");
            });
        }
    }
    
    • AddAuthentication:增加认证服务到依赖注入,注册Bearer作为默认scheme

    • AddAuthorization:增加授权服务到依赖注入,验证token中是否存在scope,这里使用的是ASP.NET Core授权策略系统

      这里实质是验证jwt中的payload的scope

    • RequireHttpsMetadata 用于测试目的;将此参数设置为 false,可在你没有证书的环境中进行测试。 在实际部署中,JWT 持有者令牌应始终只能通过 HTTPS 传递。

    • UseAuthentication:添加认证中间件,以便对host的每次调用自动执行身份认证,此中间件准备就绪后,会自动从授权标头中提取 JWT 令牌。 然后对其进行反序列化、验证,,并将其存储为用户信息,稍后供 MVC 操作或授权筛选器引用。

      JWT 持有者身份验证中间件还可以支持更高级的方案,例如颁发机构authority 不可用时使用本地证书验证令牌。 对于此情景,可以在 JwtBearerOptions 对象中指定 TokenValidationParameters 对象。

    • UseAuthorization:添加授权中间件,以确保我们的api不会被匿名客户端访问

    • RequireAuthorization("ApiScope"):全局执行授权策略

      除了全局以外,还可以针对多有的api端点,或者特定的controller,action,根据实际的业务场景灵活变化吧

    访问:http://localhost:6001/identity,返回状态码401,这是api要求凭证,所以现在api是被IdentityServer保护着

    4.创建客户端

    最后一步,创建一个由IdentityServer管理的客户端,并通过客户端请求access-token,然后访问api

    4.1 新建项目

    dotnet new console -n Client
    dotnet sln add .ClientClient.csproj
    

    4.2 引入nuget包

    需要引入IdentityModel包,一个客户端类,以请求disconvery endpoint

    cd .Client
    dotnet add package IdentityModel
    

    4.3 编码-请求Idisconvery endpoint

    只需要知道IdentityServer的基础地址,实际的各类端点地址就可以从元数据中读取:

        class Program
        {
            static async Task Main(string[] args)
            {
                // discover endpoints from metadata
                var client = new HttpClient();
                var disco = await client.GetDiscoveryDocumentAsync("http://localhost:5001");
                if (disco.IsError)
                {
                    Console.WriteLine(disco.Error);
                    return;
                }
            }
        }
    

    4.4 编码-请求access token

    这一步,使用从discovery document中获取的信息,向IdentityServer请求一个访问api1的token:

    // request token
    var tokenResponse = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
    {
        Address = disco.TokenEndpoint,
    
        //在IdentityServer注册的id与secret
        ClientId = "client app",
        ClientSecret = "secret-123456",
        Scope = "api1"
    });
    
    if (tokenResponse.IsError)
    {
        Console.WriteLine(tokenResponse.Error);
        return;
    }
    
    Console.WriteLine(tokenResponse.Json);
    

    注意看,这里的ClientId,ClientSecret必须与2.4中定义客户端保持一致,Scope也要与AllowedScopes保持一致。

    4.5 编码-调用api

    在这一步,使用扩展方法SetBearerToken,这个方法主要组装http请求:授权头+access token,并以此请求访问api资源:

    // call api
    var apiClient = new HttpClient();
    apiClient.SetBearerToken(tokenResponse.AccessToken);
    
    var response = await apiClient.GetAsync("http://localhost:6001/api/identity");
    if (!response.IsSuccessStatusCode)
    {
        Console.WriteLine(response.StatusCode);
    }
    else
    {
        var content = await response.Content.ReadAsStringAsync();
        Console.WriteLine(JArray.Parse(content));
    }
    

    5.测试

    启动IdentityServer

    cd .IdentityServer
    dotnet run
    

    启动webapi

    cd .webapi
    dotnet run
    

    用vs启动client

    获取access-token,我们通过http://jwt.calebb.net/解析

    这也是api返回的Claims

    身份认证的中间对JWT进行了身份认证后,会把解析到的Claims组装进HttpContext,以供下一个中间件(如授权中间件)调用

    接下来我们就去触发不同的错误去了解IdentityServer是如何工作的,我选择其中几个比较有意义的测试:

    5.1 使用一个无效客户端id或者密钥请求token

    没被注册的客户端,访问时,所以是invalid_client

    类比场景:去办理门禁卡,物业没找到你这个业主信息,办个鬼呀

    5.2 在请求token时指定无效的scope

    请求token,指定的scope,在indentityserver中并不存在,所以是invalid_scope

    类比场景:去办理门禁卡,小区一共10栋,你去办11栋,办个鬼呀

    5.3 请求api时,不传入toekn

    不传入token,那么webapi就没收到token,所以返回Unauthorized未授权

    类比场景:进入小区,没有门禁,肯定不让你进

    5.4 修改API对scope的验证要求

    被保护的资源webapi中配置plicy.RequireClaim("scope","api2");

    而客户端指定的scope是api1

    客户端是有access-token,具有进入系统凭证,但是,只允许scope为api2的访问,传入的时api1,当然就返回Forbidden

    类比场景:小区进入后,进入单元楼,明明是3栋2单元的楼宇,但是你的门禁只能针对3栋1单元,当然也不会刷开2单元的大门

    参考链接

    https://identityserver4.readthedocs.io/en/latest/quickstarts/1_client_credentials.html#source-code

  • 相关阅读:
    Highcharts 柱图 每个柱子外围的白色边框
    进阶ES6 点滴认知
    layer 问题 汇总
    iframe 常见问题 解析
    【腾讯开源】前端预处理器语言图形编译工具 Koala使用指南
    ruby环境sass编译中文出现Syntax error: Invalid GBK character错误解决方法
    Sass 混合宏、继承、占位符 详解
    git 命令篇
    git 继续前进篇
    zh-cn en-uk、zh-tw表示语言(文化)代码与国家地区对照表(最全的各国地区对照表)
  • 原文地址:https://www.cnblogs.com/RandyField/p/13270316.html
Copyright © 2011-2022 走看看