asp.net core系列 54 IS4用客户端凭据保护API

一. 概述

本篇开始进入IS4实战学习,从第一个示例开始,该示例是 “使用客户端凭据保护API”,这是使用IdentityServer保护api的最基本场景。该示例涉及到三个项目包括:IdentityServer项目、API项目、Client项目,都有自己的宿主,为了方便开发,放在了一个解决方案下(Quickstart.sln),三个项目的分工如下:
(1) IdentityServer项目是包含基本的IdentityServer设置的ASP.NET Core应用程序,是令牌端点。
(2) API项目是Web Api,是要保护的资源。
(3) Client项目是客户端用户,用来访问Web Api。

二. 创建IdentityServer项目

创建一个ASP.NET Core Web(或空)模板。项目名为IdentityServer,解决方案为Quickstart。是一个包含基本IdentityServer设置的ASP.NET Core应用程序。该项目使用的协议是http,当在Kestrel上运行时,端口设置为5000或在IISExpress上的随机端口。
首次启动时,IdentityServer将为您创建一个开发人员签名密钥,它是一个名为的文件tempkey.rsa。您不必将该文件检入源代码管理中,如果该文件不存在,将重新创建该文件。项目最终目录结构如下所示:



下面进行说明,以及用序号来表示开发实现步骤:
2.1 安装:Install-Package IdentityServer4
2.2 新增Config.cs文件, 该文件是IdentityServer资源和客户端配置文件。在该文件中定义API资源,以及定义客户端(可以访问此API的客户端)

/// <summary>
        /// 定义API资源,要保护的资源
        /// </summary>
        /// <returns></returns>
        public static IEnumerable<ApiResource> GetApis()
        {
            return new List<ApiResource>
            {
                new ApiResource("api1", "My API")
            };
        }
/// <summary>
        /// 定义客户端,可以访问此API的客户端
        /// </summary>
        /// <returns></returns>
        public static IEnumerable<Client> GetClients()
        {
            return new List<Client>
            {
                new Client
                {
                    ClientId = "client",

                    // no interactive user, use the clientid/secret for authentication
                    AllowedGrantTypes = GrantTypes.ClientCredentials,

                    //使用密钥进行身份认证  secret for authentication
                    ClientSecrets =
                    {
                        new Secret("secret".Sha256())
                    },

                    //客户端允许访问的范围
                    AllowedScopes = { "api1" }
                }
            };
        }

2.3 Startup配置

/// <summary>
        /// 配置IdentityServer,加载API资源和客户端
        /// </summary>
        /// <param name="services"></param>
        public void ConfigureServices(IServiceCollection services)
        {
            // uncomment, if you wan to add an MVC-based UI
            //services.AddMvc().SetCompatibilityVersion(Microsoft.AspNetCore.Mvc.CompatibilityVersion.Version_2_1);
             //添加AddIdentityServer
            var builder = services.AddIdentityServer()
                //添加内存的Identity资源
                .AddInMemoryIdentityResources(Config.GetIdentityResources())
                //添加api资源
                .AddInMemoryApiResources(Config.GetApis())
                 //添加clinet
                .AddInMemoryClients(Config.GetClients());

            if (Environment.IsDevelopment())
            {
          //开发环境下使用临时签名凭据
                builder.AddDeveloperSigningCredential();
            }
            else
            {
                throw new Exception("need to configure key material");
            }
        }
public void Configure(IApplicationBuilder app)
        {
            if (Environment.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            // uncomment if you want to support static files
            //app.UseStaticFiles();

            app.UseIdentityServer();

            // uncomment, if you wan to add an MVC-based UI
            //app.UseMvcWithDefaultRoute();
        }

运行服务器并浏览浏览器 http://localhost:5000/.well-known/openid-configuration, 客户端和API将使用它来下载必要的配置数据。下面是截取的部分配置数据:

三. 创建API项目

在解决方案下继续添加API项目,添加ASP.NET Core Web API(或空)模板。将API应用程序配置为http://localhost:5001运行。项目最终目录结构如下所示:


 (1) 在API项目中添加一个新文件夹Controllers和一个新控制器IdentityController

//定义路由
    [Route("identity")]
    //需要授权
    [Authorize]
    public class IdentityController : ControllerBase
    {
        /// <summary>
        /// 测试授权,获取该用户下声明集合Claims
        /// </summary>
        /// <returns></returns>
        public IActionResult Get()
        {
            return new JsonResult(from c in User.Claims select new { c.Type, c.Value });
        }
    }

(2) Startup配置

public void ConfigureServices(IServiceCollection services)
        {
            //将最基本的MVC服务添加到服务集合中
            services.AddMvcCore()
                //向基本的MVC服务中添加授权
                .AddAuthorization()
                //向基本的MVC服务中添加格式化
                .AddJsonFormatters();

            //将身份验证服务添加到DI服务集合中,并配置"Bearer"为默认方案  
            services.AddAuthentication("Bearer")
                //验证令牌是否有效用于此API
                .AddJwtBearer("Bearer", options =>
                {
                    options.Authority = "http://localhost:5000";
                    //在开发环境禁用,默认true
                    options.RequireHttpsMetadata = false;
            //订阅者资源范围
                    options.Audience = "api1";
                });
        }
public void Configure(IApplicationBuilder app)
        {
            //添加身份验证中间件
            app.UseAuthentication();
            app.UseMvc();
        }

启动程序运行http://localhost:5001/identity时返回401状态码,未授权。意味着API需要凭证,现在受IdentityServer保护。如下所示:

四.创建Client项目

我们通过上面知道,直接用浏览器来访问API是返回401状态码未授权,下面在Client项目中使用凭证,来获得api授权访问。下面是Client项目目录结构,这里Client是一个控制台应用程序。对于客户端可以是任意应用程序,比如手机端,web端,win服务等等。



 在IdentityServer的令牌端点实现了OAuth 2.0协议,客户端可以使用原始HTTP来访问它。但是,我们有一个名为IdentityModel的客户端库,它将协议交互封装在易于使用的API中。
3.1 安装:Install-Package IdentityModel
 3.2 发现IdentityServer端点

IdentityModel包括用于发现端点的客户端库。只需要知道IdentityServer的基地址 - 可以从元数据中读取实际的端点地址:

private static async Task Main()
        {
            // discover endpoints from metadata
            var client = new HttpClient();
            var disco = await client.GetDiscoveryDocumentAsync("http://localhost:5000");
            if (disco.IsError)
            {
                //当停掉IdentityServer服务时
                //Error connecting to http://localhost:5000/.well-known/openid-configuration: 由于目标计算机积极拒绝,无法连接。
                Console.WriteLine(disco.Error);
                return;
            }
            //...

其中GetDiscoveryDocumentAsync是属于IdentityModel库的,是对HttpClient扩展方法。http://localhost:5000是IdentityServer的基地址。
 3.3 请求令牌Token
在Mian方法中继续向IdentityServer请求令牌,访问api1资源。这里的RequestClientCredentialsTokenAsync方法也是HttpClient扩展方法。

// request token,带入需要的4个参数,请求令牌,返回TokenResponse
            var tokenResponse = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
            {
                //IdentityServer基地址 http://localhost:5000/connect/token
                Address = disco.TokenEndpoint,
                //设置客户端标识
                ClientId = "client",
                //设置密钥
                ClientSecret = "secret",
                //访问的资源范围
                Scope = "api1"
            });
            
            if (tokenResponse.IsError)
            {
                Console.WriteLine(tokenResponse.Error);
                return;
            }
            //打印 token 信息
            Console.WriteLine(tokenResponse.Json);
            Console.WriteLine("\n\n");

3.4 调用API
在Mian方法中继续向下,当访问令牌取得后,开始调用Web API。 下面将访问令牌发送到Web API,通常使用HTTP Authorization标头。这是使用SetBearerToken扩展方法完成的,该方法是IdentityModel库的HttpClient扩展方法。

// call api
            var apiClient = new HttpClient();
            //发送访问令牌
            apiClient.SetBearerToken(tokenResponse.AccessToken);

            //访问API,获取该用户下声明集合Claims
            var response = await apiClient.GetAsync("http://localhost:5001/identity");
            if (!response.IsSuccessStatusCode)
            {
                Console.WriteLine(response.StatusCode);
            }
            else
            {
                //输出 claims 名称值 对
                var content = await response.Content.ReadAsStringAsync();
                Console.WriteLine(JArray.Parse(content));
            }

下面开始测试,先启动IdentityServer程序,再启动API程序,最后启动Client客户端来访问API,通过下图可以了解到:(1)客户端请求令牌成功,(2) 客户端使用令牌来访问API成功。



如果想进一步尝试激发错误,来了解系统的行为,可以错误的去配置如下:

(1) 尝试停掉IdentityServer服务程序,这个已经测试了。

(2) 尝试使用无效的客户端ID标识 ClientId = "client",

(3) 尝试在令牌请求期间请求无效范围 Scope = "api1"

(4) 尝试在API程序未运行时调用API

(5) 尝试不要将令牌发送到API
 总结:通过本篇了解到了IS4保护api的最基本场景。流程是首先创建一个IdentityServer 令牌程序。 接着创建API项目,使用IdentityServer令牌程序来保护API。 最后创建要访问的Client项目,获取访问令牌后再调用API方法。

IdentityServer令牌端对要保护API资源做了配置 new ApiResource("api1", "My API")

限制了访问Api的客户端标识和访问资源范围ClientId = "client", AllowedScopes = { "api1" }还有客户端需要的秘钥。

参考文献

使用客户端凭据保护API

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,670评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,928评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,926评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,238评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,112评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,138评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,545评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,232评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,496评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,596评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,369评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,226评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,600评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,906评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,185评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,516评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,721评论 2 335

推荐阅读更多精彩内容