[原创] IdentityServer4权限控制---客户端创建、获取TOKEN及访问API资源(三)
经过前面两节课,我们已经完成了API服务器的搭建与IDS4身份验证服务器的搭建,如果还没有看的朋友请到这里围观:
[原创] IdentityServer4权限控制---客户端授权模式之API服务器搭建(一)
[原创] IdentityServer4权限控制---客户端授权模式之IDS4认证服务器搭建(二)
API服务器是我们要保护的资源服务器,我们希望只授权给通过身份验证的客户端去访问,而身份验证的工作是由我们搭建的IDS4服务器来完成的。现在我们模拟一个场景,我们已经将上面两台服务器成功部署到公司的服务器上去了,现在有个第三方用户,他开发了一头桌面应用,叫做client.exe,该应用在成功运行以后要读取我们的资源服务器上的接口数据,也就是访问 https://localhost:6001/上面的三个接口数据,我们希望只对身份已经证的用户开放,并不是随便人写几行代码就把我们的数据给偷走了,这里CLIENT.EXE就是我们的客户端,为此我简单画了一张图,如下:
(注意:步骤4、5、5'是假想的,我们要通过实验去验证它是否存在)
客户端程序为了访问API资源服务器上的接口数据,先要进行身份验证才可以,不然会被资源服务器拒绝,所以,它要先去IDS4服务器申请TOKEN,申请TOKEN需要携带必要的身份信息,上图左下角橘色框中便是。成功拿到TOKEN以后,客户端再发起一个网络连接,去申请API资源,这个过程中要向API资源服务器提交身份认证用的TOKEN,右下角绿色便是。为了流程理解方便,我在上面标注了数据代表步骤顺序。资源服务器得到TOKEN以后,是JWT格式的,直接在本机验证,(这里要特别注意一下!!!,也就是说:步骤3以后直接返回,而不是通过步骤45后再返回)。
现在我们需要去打造一个客户端client.exe去申请我们的TOKEN以及访问我们的资源。
创建一个控制台程序
选择新建目录
选择.NET6.0下一步,就建好了。我们的直接用官网提供的代码去测试,如下,
// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. using IdentityModel.Client; using Newtonsoft.Json.Linq; using System; using System.Net.Http; using System.Threading.Tasks; namespace Client { public class Program { private static async Task Main() { // discover endpoints from metadata var client = new HttpClient(); var disco = await client.GetDiscoveryDocumentAsync("https://localhost:5001"); //取得证书 https://localhost:5001/.well-known/openid-configuration if (disco.IsError) { Console.WriteLine(disco.Error); return; } // 请求token令牌 var tokenResponse = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest { Address = disco.TokenEndpoint, ClientId = "client", ClientSecret = "secret", Scope = "api1" //Scope = "identity" }); if (tokenResponse.IsError) { Console.WriteLine(tokenResponse.Error); return; } Console.WriteLine(tokenResponse.Json);//输出令牌信息 Console.WriteLine("\n\n"); // call api 访问我们的API资源,注意,每个资源的权限要求是不一样的 var apiClient = new HttpClient(); apiClient.SetBearerToken(tokenResponse.AccessToken); var response = await apiClient.GetAsync("https://localhost:6001/identity"); //该资源要求登录用户才可以访问 if (!response.IsSuccessStatusCode) { Console.WriteLine(response.StatusCode); } else { var content = await response.Content.ReadAsStringAsync(); Console.WriteLine(JArray.Parse(content)); } response = await apiClient.GetAsync("https://localhost:6001/api/Students"); //该资源匿名可访问 if (!response.IsSuccessStatusCode) { Console.WriteLine(response.StatusCode); } else { var content = await response.Content.ReadAsStringAsync(); Console.WriteLine(JArray.Parse(content)); } } } }我们把IdentityModel包引起去,再把Newtonsoft.Json也引进去,在第二课中讲到,Identity 接口是要验证用户才可以访问成功的,未登录用户返回401。现在我们运行client.exe试试,运行之前先运行我们的API服务器与身份验证服务器,再运行client.exe,看到CMD窗口返回以下信息:
{"access_token":"eyJhbGciOiJSUzI1NiIsImtpZCI6IkU1OEMxMkExMUU3NTkwRTk3NEY3REREQTA3NEIwRTUwIiwidHlwIjoiYXQrand0In0.eyJuYmYiOjE2NjIxOTcyMzMsImV4cCI6MTY2MjIwMDgzMywiaXNzIjoiaHR0cHM6Ly9sb2NhbGhvc3Q6NTAwMSIsImNsaWVudF9pZCI6ImNsaWVudCIsImp0aSI6IjU3RDA4MkIxN0NFRjUxM0M4MzVFRENFNzkxQ0NGRDJGIiwiaWF0IjoxNjYyMTk3MjMzLCJzY29wZSI6WyJhcGkxIl19.pYY8nTQvHQMeKjn5LkE-OO2xzsnS4xyicusJ88SMyThqelFq95fhqZC3jUoOFqDUy8ex7S1Q_c8RL0G3dnvuIIiyg6ECPxRBNHwVfztdzVpoe7qOEyYdP_Z9lqwcgJdbWiWCGzcPkWzErikFIHDe2KwprD1_YlDBN67ze96sVWypNrSBWhFgPf80B3wfDSE3z1apoz0Pe8QqkoWpE9xT-Y0_vz5s3m-CaNl6ar8M1Wk2LdvDYwk3UgJ5p4XViwtookPUj48rKVseohe7qQtTpXpT0RXf0JLuXmAhxRMweXBZMNY64F-d9MbqftyBKlDffXTgoK8JeaujcYdZ9nLy6A","expires_in":3600,"token_type":"Bearer","scope":"api1"} [ { "type": "nbf", "value": "1662197233" }, { "type": "exp", "value": "1662200833" }, { "type": "iss", "value": "https://localhost:5001" }, { "type": "client_id", "value": "client" }, { "type": "jti", "value": "57D082B17CEF513C835EDCE791CCFD2F" }, { "type": "iat", "value": "1662197233" }, { "type": "scope", "value": "api1" } ] [ { "boinYear": 1986, "xingMing": "QiQi", "age": 36, "address": "中国" }, { "boinYear": 1970, "xingMing": "Black Smith", "age": 52, "address": "英国" }, { "boinYear": 1957, "xingMing": "John", "age": 65, "address": "日本" }, { "boinYear": 1975, "xingMing": "John", "age": 47, "address": "加拿大" }, { "boinYear": 1975, "xingMing": "QiQi", "age": 47, "address": "美国" } ]
LOOK,我们成功获取到了Identity接口的数据!我们接着文章开始处留下的问题分别做这样的实验:
一、在 client.exe从identityserver4获得tokenResponse.AccessToken后,将identityserver4服务器关闭,分别访问两个API接口。
二、在获得tokenResponse.AccessToken后,将它保存下来,然后关闭identityserver4服务器,也就是说,用上次获得到TOKEN,在验证服务器关闭的状态下去访问API资源。
通过上面的实验,我们知道,在identityserver4关闭的时候,我们的身份验证照常可以通过,也就是说,登录获得TOKEN是在Identityserver4上进行的,而获得TOKEN以后就没它什么事了。文章开始处的流程图中4、5、5‘ 步骤是不存在的。
这样,我们的客户端就只有通过了身份验证才可以访问到我们的资源,也就是说资源得到了保护。但是很多小伙伴要问了,你这也太粗暴了吧!我怎么样对API的资源进行更精细化的保护呢? 别着急,我们打开APITEST项目,在program.cs 中写一个权限策略来实现更精细化的控制。
builder.Services.AddAuthorization(options => { options.AddPolicy("ApiScope", policy => { policy.RequireAuthenticatedUser(); policy.RequireClaim("scope", "api1"); }); });就这样,我们在program.cs启动之时注册了一个安全策略,我们只需要在需要验证的地放这样去引用就可以了
//[Authorize] [Authorize(Policy = "ApiScope")]这样就不是所有登录的用户都可以访问我们的资源了,而是需要满足ApiScope策略的用户才可以!根据这个原理,我们可以扩展很多。IDS4官方宣称,给资源一个别名是比较推荐的一种做法,给了别名,其实就可以用不同的别名来授权。尝试将API服务器换成域名,和IDS4分别不同的域名,让客户端跨域申请TOKEN及授权,任然是可以的,看来还是比较方便的,其它功能等后面研究再深入一些,我再回来补充。。。
今天的源码下载
看文章一天,写文章一天,写作不易,朋友们留言支持一下就不胜感激了。。。
原创文章,创作不易,转载请注明出处:https://www.qhwins.com/article-29