本文共 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
参数,表明了客户端的访问权限
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
使用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
测试环境,使用http,删掉IIS相关的配置
{ "profiles": { "SelfHost": { "commandName": "Project", "launchBrowser": true, "launchUrl": ".well-known/openid-configuration", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" }, "applicationUrl": "http://localhost:5001/" } }}
上篇与前文都介绍过,scope
代表资源所有者在被保护资源那里的一些权限,可以把被保护资源分为不同的scope
,具体的粒度由开发自定义。
模板中ApiScope为空,在Config.cs增加
public static IEnumerableApiScopes =>new ApiScope[]{ //新增 new ApiScope("api1", "My API")};
第二个参数,是displayname
要让我们的IdentityServer给客户端颁发token,就要让客户端在IdentityServer注册。
客户端,模板中的客户端与scope一样为空,在Config.cs增加客户端,代码如下:
public static IEnumerableClients => 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应用程序,想访问服务器)。
注释模板代码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的文件。您不必将该文件签入源代码版本控制,如果不存在该文件,它将被重新创建。
限制开始创建我们需要保护的api资源
dotnet new webapi -n webapicd ..dotnet sln add .\webapi\webapi.csproj
{ "profiles": { "Api": { "commandName": "Project", "launchBrowser": true, "applicationUrl": "http://localhost:6001", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } } }}
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 }); } }
Microsoft.AspNetCore.Authentication.JwtBearer
这个包是当收到请求时,对授权头中JWT的具体身份认证
dotnet add .\webapi\webapi.csproj package Microsoft.AspNetCore.Authentication.JwtBearer
最后一步是将身份认证服务添加到依赖注入中,并将身份认证中间件添加到管道中。以便:
验证传入的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保护着
最后一步,创建一个由IdentityServer管理的客户端,并通过客户端请求access-token,然后访问api
dotnet new console -n Clientdotnet sln add .\Client\Client.csproj
需要引入IdentityModel
包,一个客户端类,以请求disconvery endpoint
cd .\Client\dotnet add package IdentityModel
只需要知道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; } } }
这一步,使用从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
保持一致。
在这一步,使用扩展方法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));}
启动IdentityServer
cd .\IdentityServer\dotnet run
启动webapi
cd .\webapi\dotnet run
用vs启动client
获取access-token,我们通过http://jwt.calebb.net/解析
这也是api返回的Claims
“身份认证的中间对JWT进行了身份认证后,会把解析到的Claims组装进HttpContext,以供下一个中间件(如授权中间件)调用
”
接下来我们就去触发不同的错误去了解IdentityServer是如何工作的,我选择其中几个比较有意义的测试:
没被注册的客户端,访问时,所以是invalid_client
类比场景:去办理门禁卡,物业没找到你这个业主信息,办个鬼呀
请求token,指定的scope,在indentityserver中并不存在,所以是invalid_scope
类比场景:去办理门禁卡,小区一共10栋,你去办11栋,办个鬼呀
不传入token,那么webapi就没收到token,所以返回Unauthorized
未授权
类比场景:进入小区,没有门禁,肯定不让你进
被保护的资源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/