重新整理|重新整理 .net core 实践篇——— 权限源码阅读四十五]

前言
简单介绍一下权限源码阅读一下。
正文
一直有人对授权这个事情上争论不休,有的人认为在输入账户密码给后台这个时候进行了授权,因为认为发送了一个身份令牌,令牌里面可能有些用户角色信息,认为这就是授权,有的人认为这只是获取令牌的过程。
现实生活中有一个是授权证书,那么有人认为token 是授权证书,但这只是颁发证书。账户密码获取获取身份令牌也不是认证,认证是证明你的身份令牌有效的过程。
那么netcore 中是如何解释授权的:
重新整理|重新整理 .net core 实践篇——— 权限源码阅读四十五]
文章图片

授权是指确定用户可执行的操作的过程。故而实际上,获取身份令牌只是获取令牌,授权是指在访问过程中,确认是否可以访问的过程。身份令牌中有角色,有些是根据角色还确定是否可以访问的,这就是授权了。
不过随着业务的复杂,网关可以根据角色授权接口,也可以根据自己的策略了,授权的过程五花八门的。
在网关中一般有认证和授权两部分,先认证再授权,先确定合法身份在来确定一下授权。
先来看下认证吧,有些人一认证就想到了jwt,或者想到了具体的认证方式,其实认证就是你的系统认为它符合了合法身份,和具体的东西没有关系,是一个抽象的概念。

app.UseAuthentication();

通过上面这个看下认证过程。
然后看下具体的中间件。
public AuthenticationMiddleware(RequestDelegate next, IAuthenticationSchemeProvider schemes) { if (next == null) { throw new ArgumentNullException(nameof(next)); } if (schemes == null) { throw new ArgumentNullException(nameof(schemes)); } _next = next; Schemes = schemes; }

从这里看呢,IAuthenticationSchemeProvider 提供了认证解决方案,可以看下这个接口。
/// /// Responsible for managing what authenticationSchemes are supported. /// public interface IAuthenticationSchemeProvider { /// /// Returns all currently registered s. /// /// All currently registered s. Task GetAllSchemesAsync(); /// /// Returns thematching the name, or null. /// /// The name of the authenticationScheme. /// The scheme or null if not found. Task GetSchemeAsync(string name); /// /// Returns the scheme that will be used by default for . /// This is typically specified via . /// Otherwise, this will fallback to . /// /// The scheme that will be used by default for . Task GetDefaultAuthenticateSchemeAsync(); /// /// Returns the scheme that will be used by default for . /// This is typically specified via . /// Otherwise, this will fallback to . /// /// The scheme that will be used by default for . Task GetDefaultChallengeSchemeAsync(); /// /// Returns the scheme that will be used by default for . /// This is typically specified via . /// Otherwise, this will fallback to. /// /// The scheme that will be used by default for . Task GetDefaultForbidSchemeAsync(); /// /// Returns the scheme that will be used by default for . /// This is typically specified via . /// Otherwise, this will fallback to . /// /// The scheme that will be used by default for . Task GetDefaultSignInSchemeAsync(); /// /// Returns the scheme that will be used by default for . /// This is typically specified via . /// Otherwise, this will fallback to. /// /// The scheme that will be used by default for . Task GetDefaultSignOutSchemeAsync(); /// /// Registers a scheme for use by . /// /// The scheme. void AddScheme(AuthenticationScheme scheme); /// /// Registers a scheme for use by . /// /// The scheme. /// true if the scheme was added successfully. bool TryAddScheme(AuthenticationScheme scheme) { try { AddScheme(scheme); return true; } catch { return false; } } /// /// Removes a scheme, preventing it from being used by . /// /// The name of the authenticationScheme being removed. void RemoveScheme(string name); /// /// Returns the schemes in priority order for request handling. /// /// The schemes in priority order for request handling Task GetRequestHandlerSchemesAsync(); }

虽然没有看到具体的provider,但是呢,可以通过接口注释看个大概哈。
比如说AuthenticationScheme 是认证方案的意思,从英文表面理解哈。然后里面有方法增删改查,意味着我们可以有多种认证方式。
这其实是刚需,因为比如以前颁发的身份令牌和现在接口颁发的身份令牌不一样了,那么为了无缝衔接,可以认可两种认证方式。
那么看下AuthenticationScheme 认证方案里面有些啥吧。
/// /// AuthenticationSchemes assign a name to a specific /// handlerType. /// public class AuthenticationScheme { /// /// Initializes a new instance of . /// /// The name for the authentication scheme. /// The display name for the authentication scheme. /// Thetype that handles this scheme. public AuthenticationScheme(string name, string? displayName, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type handlerType) { if (name == null) { throw new ArgumentNullException(nameof(name)); } if (handlerType == null) { throw new ArgumentNullException(nameof(handlerType)); } if (!typeof(IAuthenticationHandler).IsAssignableFrom(handlerType)) { throw new ArgumentException("handlerType must implement IAuthenticationHandler."); }Name = name; HandlerType = handlerType; DisplayName = displayName; } /// /// The name of the authentication scheme. /// public string Name { get; } /// /// The display name for the scheme. Null is valid and used for non user facing schemes. /// public string? DisplayName { get; } /// /// Thetype that handles this scheme. /// [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] public Type HandlerType { get; } }

这里面有name 和displayname,一个是名称,一个是显示名称,相信很多人都见到过这样的类,里面有name 还有 displayname。
不要那么计较,显示名称是为了好大家好而已。比如说我们的sex 表示性别,那么displayname 可以写显示名称。
比如说你的一个计划类,里面可以有name 和 displayname。name 是JC159,displayname 是瞎扯计划,JC159 多难理解啊,瞎扯计划多好理解,瞎扯啊。
然后里面有一个是HandlerType,叫做处理类型,处理认证计划的类型。上面有注释,这个类型继承IAuthenticationHandler这个接口,那么也就是这个方案将由实现IAuthenticationHandler的类来处理,具体看实际的处理方案,比如jwt。
然后看下中间件的invoke。
/// /// Invokes the middleware performing authentication. /// /// The . public async Task Invoke(HttpContext context) { context.Features.Set(new AuthenticationFeature { OriginalPath = context.Request.Path, OriginalPathBase = context.Request.PathBase }); // Give any IAuthenticationRequestHandler schemes a chance to handle the request var handlers = context.RequestServices.GetRequiredService(); foreach (var scheme in await Schemes.GetRequestHandlerSchemesAsync()) { var handler = await handlers.GetHandlerAsync(context, scheme.Name) as IAuthenticationRequestHandler; if (handler != null && await handler.HandleRequestAsync()) { return; } } var defaultAuthenticate = await Schemes.GetDefaultAuthenticateSchemeAsync(); if (defaultAuthenticate != null) { var result = await context.AuthenticateAsync(defaultAuthenticate.Name); if (result?.Principal != null) { context.User = result.Principal; } } await _next(context); }

一段一段看吧。
context.Features.Set(new AuthenticationFeature { OriginalPath = context.Request.Path, OriginalPathBase = context.Request.PathBase });

Features 是经过一个集合,比如我们经过中间件,我们可以向里面写入一些东西,然后供下一个中间件使用都行,有点像是游戏里面背包的功能。
var handlers = context.RequestServices.GetRequiredService(); foreach (var scheme in await Schemes.GetRequestHandlerSchemesAsync()) { var handler = await handlers.GetHandlerAsync(context, scheme.Name) as IAuthenticationRequestHandler; if (handler != null && await handler.HandleRequestAsync()) { return; } }

这里面就是获取就是获取相应的认真方案处理器,然后进行执行HandleRequestAsync。
值得主意的是,这里面并不是执行IAuthenticationHandler的方法,而是IAuthenticationRequestHandler的方法。
所以这并不意味这我们的写入的每个方案都必须通过,而是如果我们的写入的每个认证方案继承IAuthenticationRequestHandler,那么必须通过其中的HandleRequestAsync方法。
然后看下IAuthenticationRequestHandler 这个哈。
/// /// Used to determine if a handler wants to participate in request processing. /// public interface IAuthenticationRequestHandler : IAuthenticationHandler { /// /// Gets a value that determines if the request should stop being processed. /// /// This feature is supported by the Authentication middleware /// which does not invoke any subsequentor middleware configured in the request pipeline /// if the handler returns . /// /// /// if request processing should stop. Task HandleRequestAsync(); }

继续往下看:
var defaultAuthenticate = await Schemes.GetDefaultAuthenticateSchemeAsync(); if (defaultAuthenticate != null) { var result = await context.AuthenticateAsync(defaultAuthenticate.Name); if (result?.Principal != null) { context.User = result.Principal; } }

继续往下看哈,然后里面也有这个哈,如果有默认的认证方案,那么context.User 会通过默认认证方案的处理器进行获取。也就是说如果我们设置了默认方案,那么就会通过默认方案来进行认证。
await _next(context);

这个表示继续往下执行了。
那么来看下具体服务的认证吧,比如说jwt的。
services.AddAuthentication("Bearer") // 添加JwtBearer服务 .AddJwtBearer(o => { o.TokenValidationParameters = tokenValidationParameters; o.Events = new JwtBearerEvents { OnAuthenticationFailed = context => { // 如果过期,则把<是否过期>添加到,返回头信息中 if (context.Exception.GetType() == typeof(SecurityTokenExpiredException)) { context.Response.Headers.Add("Token-Expired", "true"); } return Task.CompletedTask; } }; });

首先来看一下:
services.AddAuthentication("Bearer")

这里面就是设置默认的认证方案:
public static AuthenticationBuilder AddAuthentication(this IServiceCollection services, string defaultScheme) => services.AddAuthentication(o => o.DefaultScheme = defaultScheme); public static AuthenticationBuilder AddAuthentication(this IServiceCollection services, Action configureOptions) { if (services == null) { throw new ArgumentNullException(nameof(services)); } if (configureOptions == null) { throw new ArgumentNullException(nameof(configureOptions)); } var builder = services.AddAuthentication(); services.Configure(configureOptions); return builder; }

看一下:var builder = services.AddAuthentication();
这个哈,这个才是具体增加具体服务的。
public static AuthenticationBuilder AddAuthentication(this IServiceCollection services) { if (services == null) { throw new ArgumentNullException(nameof(services)); } services.AddAuthenticationCore(); services.AddDataProtection(); services.AddWebEncoders(); services.TryAddSingleton(); return new AuthenticationBuilder(services); }

然后看下services.AddAuthenticationCore(); ,为什么看下这个呢?难道我提前看了这个东西吗?
不是,因为我们知道分层的时候有个Core的层,是具体实现的,那么这种带core 一般就是具体实现方式了。
public static IServiceCollection AddAuthenticationCore(this IServiceCollection services) { if (services == null) { throw new ArgumentNullException(nameof(services)); } services.TryAddScoped(); services.TryAddSingleton(); // Can be replaced with scoped ones that use DbContext services.TryAddScoped(); services.TryAddSingleton(); return services; }

前面我们看了这个IAuthenticationHandlerProvider 和IAuthenticationSchemeProvider ,那么这里可以看到他们的具体实现是AuthenticationHandlerProvider和AuthenticationSchemeProvider。
前面提及到会通过handletype来获取具体的处理器,那么来看下具体怎么实现的吧。
/// /// Returns the handler instance that will be used. /// /// The context. /// The name of the authentication scheme being handled. /// The handler instance. public async Task GetHandlerAsync(HttpContext context, string authenticationScheme) { if (_handlerMap.ContainsKey(authenticationScheme)) { return _handlerMap[authenticationScheme]; } var scheme = await Schemes.GetSchemeAsync(authenticationScheme); if (scheme == null) { return null; } var handler = (context.RequestServices.GetService(scheme.HandlerType) ?? ActivatorUtilities.CreateInstance(context.RequestServices, scheme.HandlerType)) as IAuthenticationHandler; if (handler != null) { await handler.InitializeAsync(scheme, context); _handlerMap[authenticationScheme] = handler; } return handler; }

看这个GetHandlerAsync,是通过依赖注入的方式来获取的,根据方案里面的GetHandlerAsync。
那么从这里就能猜到jwt的具体实现了,那么直接来看吧。
services.AddAuthentication("Bearer") // 添加JwtBearer服务 .AddJwtBearer(o => { o.TokenValidationParameters = tokenValidationParameters; o.Events = new JwtBearerEvents { OnAuthenticationFailed = context => { // 如果过期,则把<是否过期>添加到,返回头信息中 if (context.Exception.GetType() == typeof(SecurityTokenExpiredException)) { context.Response.Headers.Add("Token-Expired", "true"); } return Task.CompletedTask; } }; });

其实不建议这么写的,应该是:
重新整理|重新整理 .net core 实践篇——— 权限源码阅读四十五]
文章图片

直接标明这里使用的策略,之所以这个能够生效,是因为默认的是
重新整理|重新整理 .net core 实践篇——— 权限源码阅读四十五]
文章图片

红框框部分是Bearer,但是不友好,对框架不熟,容易形成误导。
继续往下看:
public static AuthenticationBuilder AddJwtBearer(this AuthenticationBuilder builder, string authenticationScheme, string displayName, Action configureOptions) { builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton, JwtBearerPostConfigureOptions>()); return builder.AddScheme(authenticationScheme, displayName, configureOptions); }

AddScheme 就是具体的注入了:
/// /// Adds awhich can be used by . /// /// Thetype to configure the handler."/>. /// Theused to handle this scheme. /// The name of this scheme. /// The display name of this scheme. /// Used to configure the scheme options. /// The builder. public virtual AuthenticationBuilder AddScheme(string authenticationScheme, string displayName, Action configureOptions) where TOptions : AuthenticationSchemeOptions, new() where THandler : AuthenticationHandler => AddSchemeHelper(authenticationScheme, displayName, configureOptions);

然后加入到认证方案中去,JwtBearerOptions 就是这个方案的配置,JwtBearerHandler就是具体的处理,看下AddSchemeHelper。
private AuthenticationBuilder AddSchemeHelper(string authenticationScheme, string displayName, Action configureOptions) where TOptions : class, new() where THandler : class, IAuthenticationHandler { Services.Configure(o => { o.AddScheme(authenticationScheme, scheme => { scheme.HandlerType = typeof(THandler); scheme.DisplayName = displayName; }); }); if (configureOptions != null) { Services.Configure(authenticationScheme, configureOptions); } Services.AddTransient(); return this; }

分步骤看下:
Services.Configure(o => { o.AddScheme(authenticationScheme, scheme => { scheme.HandlerType = typeof(THandler); scheme.DisplayName = displayName; }); });

这一步就是添加具体的认证方案。
if (configureOptions != null) { Services.Configure(authenticationScheme, configureOptions); } Services.AddTransient();

这一步就是注入配置文件,并且将处理器注入到ioc中,这里就是JwtBearerHandler了。
JwtBearerHandler 就不看了,就是一些具体的实现,根据配置文件,然后处理,就属于jwt的知识了。
补充 这里扩容一下配置的知识,主要解释一下JwtBearerHandler 是如何根据不同的authenticationScheme 获取不同的配置的。
Services.Configure(authenticationScheme, configureOptions);

public static IServiceCollection Configure(this IServiceCollection services, string name, Action configureOptions) where TOptions : class { if (services == null) { throw new ArgumentNullException(nameof(services)); } if (configureOptions == null) { throw new ArgumentNullException(nameof(configureOptions)); } services.AddOptions(); services.AddSingleton>(new ConfigureNamedOptions(name, configureOptions)); return services; }

看到吧,实际上获IConfigureOptions ,会获取一组ConfigureNamedOptions,然后通过name筛选出来。
看下JwtBearerHandler:
public class JwtBearerHandler : AuthenticationHandler public JwtBearerHandler(IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder, IDataProtectionProvider dataProtection, ISystemClock clock) : base(options, logger, encoder, clock) { }

将options 传给了AuthenticationHandler。
那么看下AuthenticationHandler 中如何处理的吧。
初始化的时候:
/// /// Initialize the handler, resolve the options and validate them. /// /// /// /// public async Task InitializeAsync(AuthenticationScheme scheme, HttpContext context) { if (scheme == null) { throw new ArgumentNullException(nameof(scheme)); } if (context == null) { throw new ArgumentNullException(nameof(context)); } Scheme = scheme; Context = context; Options = OptionsMonitor.Get(Scheme.Name) ?? new TOptions(); Options.Validate(Scheme.Name); await InitializeEventsAsync(); await InitializeHandlerAsync(); }

进行一波筛选而来的哈。

【重新整理|重新整理 .net core 实践篇——— 权限源码阅读四十五]】下一节看下授权的源码吧。

    推荐阅读