前段时间写了一篇基于.NetCore环境使用IdentityServer4为API接口鉴权的文章,更多的是从快速上手的角度描述了IdentityServer4的使用。后续使用过程中,自己有了一些其他想法和困惑,于是便进行一番探索,在这里记录分享一下。

本文主要和大家认识下Client获取到Token之后,请求API资源时具体是如何实现身份认证和资源授权的。

解决以下困惑:

1.Client请求时携带的Token具体是如何实现认证的?

2.Token中自包含的信息(IdentityServer4使用的Token实际是JWT类型)是如何传递到API资源服务器中的?

3.为什么被标识了【Authorize】特性或者被全局设置过滤器AuthorizeFilter的资源就能实现鉴权,系统是如何实现的?

准备环境:

1.IdentityServer4授权服务

2.API资源服务

3.Postman模拟发起http请求的客户端

由于之前文章讲解过如何实现IdentityServer鉴权,本文就不再赘述如何搭建项目环境,感兴趣的同学可以看下之前的文章IdentityServer4实现.Net Core API接口权限认证

关注下代码核心部分:

API资源服务中注册认证服务

        public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();//注册身份认证服务
services.AddAuthentication("Bearer")
.AddIdentityServerAuthentication(options =>
{
options.Authority = Configuration.GetValue<string>("Authority:Url");//你要请求验证的identity服务端的地址
options.RequireHttpsMetadata = false;
options.ApiName = "api";//你选择的验证方式。 对应的GetClients中定义的作用域
}); }

1)AddAuthentication内部注册了我们最主要的三个对象AuthenticationService, AuthenticationHandlerProvider, AuthenticationSchemeProvider

2)AddIdentityServerAuthentication(或者AddCookie、AddJwtBearer)指定Scheme类型和需要验证的参数

API资源服务中添加身份认证、授权管道中间件:

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
app.UseRouting();
//添加权限认证中间件
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}

1) app.UseAuthentication() 添加了中间件AuthenticationMiddleware

2) app.UseAuthorization() 添加了中间件AuthorizationMiddleware

.NetCore中Http请求过程:

Http请求 > 其它中间件 > 路由中间件(这里就拿到终点路由了) > 身份验证中间件 > 授权中间件 > MVC中间件 > Controller > Action

深入认识下身份认证和授权两个过程:

发起API接口请求(header中参数Authorization携带token)

Http请求先经过身份认证AuthenticationMiddleware中间件请求管道,然后经过授权AuthorizationMiddleware中间件请求管道。

1.身份认证 即token认证

借用一张大佬的图,给小伙伴们解解渴。

结合上面这张图,我们来详细看看这其中到底是如何实现的?

API服务一共向IdentityServer4授权服务请求了两次

1)第一次请求IdentityServer4授权服务,指向终点路由DiscoveryEndPoint(/.well-known/openid-configuration),获取IdentityServer授权服务配置信息

{
"issuer": "http://localhost:7000",
"jwks_uri": "http://localhost:7000/.well-known/openid-configuration/jwks",
"authorization_endpoint": "http://localhost:7000/connect/authorize",
"token_endpoint": "http://localhost:7000/connect/token",
"userinfo_endpoint": "http://localhost:7000/connect/userinfo",
"end_session_endpoint": "http://localhost:7000/connect/endsession",
"check_session_iframe": "http://localhost:7000/connect/checksession",
"revocation_endpoint": "http://localhost:7000/connect/revocation",
"introspection_endpoint": "http://localhost:7000/connect/introspect",
"device_authorization_endpoint": "http://localhost:7000/connect/deviceauthorization",
"frontchannel_logout_supported": true,
"frontchannel_logout_session_supported": true,
"backchannel_logout_supported": true,
"backchannel_logout_session_supported": true,
"scopes_supported": ["openid", "profile", "api1", "api2", "offline_access"],
"claims_supported": ["sub", "name", "family_name", "given_name", "middle_name", "nickname", "preferred_username", "profile", "picture", "website", "gender", "birthdate", "zoneinfo", "locale", "updated_at"],
"grant_types_supported": ["authorization_code", "client_credentials", "refresh_token", "implicit", "password", "urn:ietf:params:oauth:grant-type:device_code"],
"response_types_supported": ["code", "token", "id_token", "id_token token", "code id_token", "code token", "code id_token token"],
"response_modes_supported": ["form_post", "query", "fragment"],
"token_endpoint_auth_methods_supported": ["client_secret_basic", "client_secret_post"],
"id_token_signing_alg_values_supported": ["RS256"],
"subject_types_supported": ["public"],
"code_challenge_methods_supported": ["plain", "S256"],
"request_parameter_supported": true
}

2)第二次请求IdentityServer授权服务,指向终点路由DiscoveryKeyEndPoint(/.well-known/openid-configuration/jwks),获取IdentityServer授权服务中jwks信息(kid和公钥)。kid唯一标识一个token

{
"keys": [{
"kty": "RSA",
"use": "sig",
"kid": "B22FBEC9ACABEF0F9DFFD4DFD370D445",
"e": "AQAB",
"n": "45H1Uw6EFjClHK-LQpUptFWX0PrpQEqy-YVNljj8cYmrPK3Zjqgk6XyW8tSCEYpClJJnuvSrae9yyrYdCghChDkNFVU1H8PT3Y_aSYDrULfqKO39HdqF7pZoxayZpyseAHE9tYQjtAw7E5IBpnXd_02Wz4K2mpt8Z2s5hPwos2_ze1Msvdl7iPmPfNdncl2tvERkr9pM5vRvbzxA9N1aTF58W03oq6bZoIA2w28FhTedcCxpPb3euT926ribOKCtsxCKCqFiIZYc0ovAiSV-kuLhaywd9e5KA18sOObg0McOBGmCJsQ6PTkkpn_yqvb3Eu9jhgxCO-_8K3Ml_dFIXQ",
"alg": "RS256"
}]
}

3)API服务根据kid本地缓存token资源,当同一个kid再次发起请求时,不再向授权服务identityserver请求。

4)通过授权服务拿到token的kid和公钥之后,由API资源服务验证token,我们看下AuthenticationMiddleware中间件做了什么


public IAuthenticationSchemeProvider Schemes { get; set; }
public async Task Invoke(HttpContext context)
{
context.Features.Set<IAuthenticationFeature>((IAuthenticationFeature) new AuthenticationFeature()
{
OriginalPath = context.Request.Path,
OriginalPathBase = context.Request.PathBase
});
IAuthenticationHandlerProvider handlers = context.RequestServices.GetRequiredService<IAuthenticationHandlerProvider>();
foreach (AuthenticationScheme authenticationScheme in await this.Schemes.GetRequestHandlerSchemesAsync())
{
IAuthenticationRequestHandler handlerAsync = await handlers.GetHandlerAsync(context, authenticationScheme.Name) as IAuthenticationRequestHandler;
bool flag = handlerAsync != null;
if (flag)
flag = await handlerAsync.HandleRequestAsync();
if (flag)
return;
}
AuthenticationScheme authenticateSchemeAsync = await this.Schemes.GetDefaultAuthenticateSchemeAsync();
if (authenticateSchemeAsync != null)
{
//实际核心认证处理的地方
AuthenticateResult authenticateResult = await context.AuthenticateAsync(authenticateSchemeAsync.Name);
if (authenticateResult?.Principal != null)
context.User = authenticateResult.Principal;
}
await this._next(context);
}

在认证过程中, Schemes.GetRequestHandlerSchemesAsync() 通过AuthenticationSchemeProvider获取Scheme列表,

handlers.GetHandlerAsync 通过IAuthenticationHandlerProvider 获取scheme对应的handler并存在handlerMap缓存字典里,

context.AuthenticateAsync 通过Scheme和AuthenticationHandlerProvider获取实际处理请求的AuthenticationHandler(如JwtBearerHandler),最后通过实际的AuthenticationHandler的AuthenticateAsync方法进行认证流程。

解析认证后会将token(JWT格式:header+payload+签名)中的Payload主体部分赋值到当前请求上下文context.User中。

然后我们便可以在API资源服务器中通过ClaimsPrincipal—User获取解析后的相关信息。

        public ActionResult<IEnumerable<string>> Get()
{
var claims = User.Claims.Select(x => new { x.Type, x.Value }).ToList();
var claimSub = User.FindFirstValue("sub");
var claimId = User.FindFirstValue("id");
return new string[] { "Hello,WebAPI" };
}

2.资源鉴权
通过【Authorize】特性指定Controller或Action实现鉴权

        [HttpGet]
[Authorize(Roles = "admin")]
public ActionResult<IEnumerable<string>> Get()
{
return new string[] { "Hello,WebAPI" };
}

或者

通过AuthorizeFilter给Controller添加全局鉴权

       public void ConfigureServices(IServiceCollection services)
{
services.AddControllers(x => x.Filters.Add(new AuthorizeFilter()));//全局添加权限认证
//将身份验证服务添加到管道中
services.AddAuthentication("Bearer")
.AddIdentityServerAuthentication(options =>
{
options.Authority = Configuration.GetValue<string>("Authority:Url"); //你要请求验证的identity服务端的地址
options.RequireHttpsMetadata = false;
options.ApiName = "api"; //你选择的验证方式。 对应的GetClients中定义的作用域
});
}

那么.Net框架内部又是如何识别上面两种方式来实现鉴权的呢?

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseAuthorization();
}

通过 app.UseAuthorization(); 在Http请求管道中添加了授权中间件AuthorizationMiddleware。

public static IApplicationBuilder UseAuthorization(this IApplicationBuilder app)
{
if (app == null)
{
throw new ArgumentNullException(nameof(app));
}
VerifyServicesRegistered(app);
return app.UseMiddleware<AuthorizationMiddleware>();
}

AuthorizationMiddleware 中间件中,我们发现了下面这行代码:

var authorizeData = endpoint?.Metadata.GetOrderedMetadata<IAuthorizeData>() ?? Array.Empty<IAuthorizeData>();

再看IAuthorizeData,定义了授权规则信息

public interface IAuthorizeData
{
string AuthenticationSchemes { get; set; }
string Policy { get; set; }
string Roles { get; set; }
}

不难看出,正是通过 endpoint?.Metadata.GetOrderedMetadata<IAuthorizeData>() 获取了终点路由元素对应的授权规则信息。

那么又如何与AuthorizeAttribute特性和全局过滤器实现了AuthorizeFilter关联呢?继续往下看

先看AuthorizeAttribute特性

public class AuthorizeAttribute : Attribute, IAuthorizeData
{}

实际上 AuthorizeAttribute  实现了 IAuthorizeData 授权规则接口,这也就不难理解被标识了特性【Authorize】的Controller和Action是如何被发现的。

再看AuthorizeFilter过滤器,这里仅列出方法中部分代码,便于展示和理解

public class AuthorizeFilter : IAsyncAuthorizationFilter, IFilterFactory,IFilterMetadata
{
public IEnumerable<IAuthorizeData> AuthorizeData { get; } //默认构造函数中默认创建了AuthorizeAttribute 对象
public AuthorizeFilter()
: this(authorizeData: new[] { new AuthorizeAttribute() })
{
} //赋值AuthorizeData
public AuthorizeFilter(IEnumerable<IAuthorizeData> authorizeData)
{
if (authorizeData == null)
{
throw new ArgumentNullException(nameof(authorizeData));
} AuthorizeData = authorizeData;
}
}

实际上 AuthorizeFilter  在默认构造函数中实例化了 AuthorizeAttribute 对象,并且完成了 IAuthorizeData 集合的赋值。

这也就能理解为什么通过 IAuthorizeData 可以获取到过滤器AuthorizeFilter 对应的终点路由元素的授权规则信息。

从本质上来说以上两种方式都是关联实现了接口 IAuthorizeData ,因此直接通过 endpoint?.Metadata.GetOrderedMetadata<IAuthorizeData>() 便可以获取到终点路由元素的授权规则信息。

我相信看了上述文章之后,最开始的3个疑惑大家应该内心都有自己的答案了。

1.client请求的时携带的Token具体是如何实现认证的?

首先API资源服务向IdentityServer认证服务请求获取token的公钥,然后API服务根据公钥解密token的加签部分,认证token是否有效。

2.Token中自包含的信息是如何传递到API资源服务器中的?

IdentityServer认证服务使用的是JWT类型的token,自包含了用户信息。当API服务认证token有效后,会在AuthenticationMiddleware中间件中对token进行解析获取token中payload部分的信息,并赋值到当前请求的context.User上下文当中。

3.为什么被标识了【Authorize】特性或者被全局设置过滤器AuthorizeFilter的资源就能实现鉴权,系统是如何实现的?

AuthorizationMiddleware授权中间件中通过 endpoint?.Metadata.GetOrderedMetadata<IAuthorizeData>() 获取终点路由元素的授权规则,实现API资源鉴权。

深入了解身份认证和授权机制,看看API请求到底发生了什么?的更多相关文章

  1. Angular SPA基于Ocelot API网关与IdentityServer4的身份认证与授权(四)

    在上一讲中,我们已经完成了一个完整的案例,在这个案例中,我们可以通过Angular单页面应用(SPA)进行登录,然后通过后端的Ocelot API网关整合IdentityServer4完成身份认证.在 ...

  2. Angular SPA基于Ocelot API网关与IdentityServer4的身份认证与授权(二)

    上文已经介绍了Identity Service的实现过程.今天我们继续,实现一个简单的Weather API和一个基于Ocelot的API网关. 回顾 <Angular SPA基于Ocelot ...

  3. .NetCore WebApi——基于JWT的简单身份认证与授权(Swagger)

    上接:.NetCore WebApi——Swagger简单配置 任何项目都有权限这一关键部分.比如我们有许多接口.有的接口允许任何人访问,另有一些接口需要认证身份之后才可以访问:以保证重要数据不会泄露 ...

  4. Angular SPA基于Ocelot API网关与IdentityServer4的身份认证与授权(一)

    好吧,这个题目我也想了很久,不知道如何用最简单的几个字来概括这篇文章,原本打算取名<Angular单页面应用基于Ocelot API网关与IdentityServer4+ASP.NET Iden ...

  5. Angular SPA基于Ocelot API网关与IdentityServer4的身份认证与授权(三)

    在前面两篇文章中,我介绍了基于IdentityServer4的一个Identity Service的实现,并且实现了一个Weather API和基于Ocelot的API网关,然后实现了通过Ocelot ...

  6. (转)Asp.Net MVC中身份认证和授权

    MVC自带的ActionFilter 在Asp.Net WebForm的中要做到身份认证微软为我们提供了三种方式,其中最常用的就是我们的Form认证,需要配置相应的信息.例如下面的配置信息: < ...

  7. Core篇——初探Core的认证,授权机制

    目录 1.Cookie-based认证的实现 2.Jwt Token 的认证与授权 3.Identity Authentication + EF 的认证 Cookie-based认证的实现 cooki ...

  8. ASP.NET Core 3.0 gRPC 身份认证和授权

    一.开头聊骚 本文算是对于 ASP.NET Core 3.0 gRPC 研究性学习的最后一篇了,以后在实际使用中,可能会发一些经验之文.本文主要讲 ASP.NET Core 本身的认证授权和gRPC接 ...

  9. OAuth2.0认证和授权机制讲解

    第一章.OAuth2.0 介绍 OAuth认证 OAuth认证是为了做到第三方应用在未获取到用户敏感信息(如:账号密码.用户PIN等)的情况下,能让用户授权予他来访问开放平台(主要访问平台中的资源服务 ...

  10. [认证授权] 4.OIDC(OpenId Connect)身份认证授权(核心部分)

    0 目录 认证授权系列:http://www.cnblogs.com/linianhui/category/929878.html 1 什么是OIDC? 看一下官方的介绍(http://openid. ...

随机推荐

  1. [FAQ] Sortable 拖拽组件, 火狐浏览器中打开新窗口问题

      Q:用了 sortable 组件,在火狐浏览器中进行拖拽时,会打开新窗口 ? Sortable组件地址,https://github.com/SortableJS/Sortable 当前处理方式 ...

  2. IDA动态调试快捷键

    1. F2下断点2. F7进入函数,F8单步调试,F9跳到下一个断点,F2下断点,G调到函数地址3. N重名4. g跳到地址和函数名5. u取消把函数汇编变成机器码6. c就是把机器码变成汇编7. F ...

  3. WPF 编写一个测试 WM_TOUCH 触摸消息延迟的应用

    我听说在 Win10 到 Win11 的系统版本左右,微软加上了一大波触摸性能优化,准确来说是 HID 性能优化.我想测试一下在这些系统下,采用从 Windows 消息接收到 WM_TOUCH 触摸消 ...

  4. jqGrid--统计列

    //数据表格 <div class="gridPanel" style="width:100%;"> @* 数据表格 *@ <table id ...

  5. STLINK/V2下载器接线方法

    一.ST-LINK ST-LINK产品如下图所示: ST-LINK接口定义如下图所示 ST-LINK与stm32接线 使用SW接法只需要四根线: STM32 ST-LINK VCC(3.3V) TVC ...

  6. ElasticView 是一款用来监控elasticsearch状态和操作elasticsearch索引的web可视化工具。

    ElasticView 是一款用来监控elasticsearch状态和操作elasticsearch索引的web可视化工具. 它由golang开发而成,具有部署方便,占用内存小等优点 es连接树管理( ...

  7. 几种常见Ruby on Rails内置方法介绍

    Ruby on Rails是一个功能强大的WEB开发框架,在这里我们将会学到一些经常用到的Ruby on Rails内置方法,帮助大家熟练掌握其应用技巧. Ruby on Rails自动生成文档技巧大 ...

  8. linux网络管理及常用网络工具详解

    linux网络管理及常用网络工具详解 目录 linux网络管理及常用网络工具详解 1. linux网络管理 1.1 centos网卡命名规则 1.2 域名解析配置文件 1.3 ifconfig命令管理 ...

  9. cesium教程2-加载显示地形地图

    上面地形数据,是调用cesium官方的地图服务,需要先注册cesium账户,配置cesium的账户token才行 1.在线地形服务的示例代码如下 <!DOCTYPE html> <h ...

  10. C 语言编程 — 数据类型的别名

    目录 文章目录 目录 前文列表 typedef 关键字 typedef 和预处理器指令 #define 的区别 前文列表 <程序编译流程与 GCC 编译器> <C 语言编程 - 基本 ...