博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
【One by One系列】IdentityServer4(二)使用Client Credentials保护API资源
阅读量:4034 次
发布时间:2019-05-24

本文共 9568 字,大约阅读时间需要 31 分钟。

书接上回,我们将会正式开始介绍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

    • Identity资源表示提供给客户端进行用户识别的信息(声明)。声明可能包括用户名称、电子邮件地址等。

    • API资源表示用户可通过访问令牌访问的受保护数据或功能。API 资源的一个示例是要求授权的 Web API(或 API集合)。

    • 用于签名的凭据(credentials)

    • 用户可能会请求访问的Identity资源和API资源

    • 会请求获取token的客户端

    • 用户信息的存储机制,如ASP.NET Core Identity或者其他机制

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

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

public IServiceProvider ConfigureServices(IServiceCollection services){    //...    services.AddSingleton
();    services.AddIdentityServer()        .AddSigningCredential("CN=sts")        .AddInMemoryApiResources(MyApiResourceProvider.GetAllResources())        .AddAspNetIdentity
();    //...}

经过上面的概述,我们先易后难,将从最简单的客户端凭证开始实战搭建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 quickstartcd quickstartmd srccd src#空模板 项目dotnet new is4empty -n IdentityServerThe template "IdentityServer4 Empty" was created successfully.
  • 项目添加解决方案

cd ..dotnet new sln -n QuickStartdotnet sln add .\IdentityServer\IdentityServer.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
 ApiScopes =>new ApiScope[]{    //新增    new ApiScope("api1", "My API")};
  • 第二个参数,是displayname

2.4 定义一个客户端

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

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

 public static IEnumerable
 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 webapicd ..dotnet sln add  .\webapi\webapi.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 .\webapi\webapi.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 Clientdotnet sln add .\Client\Client.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 tokenvar 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 apivar 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

长按二维码关注

点外卖,先领券

转载地址:http://kdudi.baihongyu.com/

你可能感兴趣的文章
C语言位扩展
查看>>
linux dump_backtrace
查看>>
linux irqdebug
查看>>
git 常用命令
查看>>
linux位操作API
查看>>
snprintf 函数用法
查看>>
uboot.lds文件分析
查看>>
uboot start.s文件分析
查看>>
没有路由器的情况下,开发板,虚拟机Ubuntu,win10主机,三者也可以ping通
查看>>
本地服务方式搭建etcd集群
查看>>
安装k8s Master高可用集群
查看>>
忽略图片透明区域的事件(Flex)
查看>>
忽略图片透明区域的事件(Flex)
查看>>
AS3 Flex基础知识100条
查看>>
Flex动态获取flash资源库文件
查看>>
flex中设置Label标签文字的自动换行
查看>>
Flex 中的元数据标签
查看>>
flex4 中创建自定义弹出窗口
查看>>
01Java基础语法-11. 数据类型之间的转换
查看>>
01Java基础语法-13. if分支语句的灵活使用
查看>>