Authentication — (3.1) 认证处理器的实现之AuthenticationHandler抽象基类

前文说到,AspNet/HttpAbstractions项目中定义了IAuthenticationHandler接口,负责针对每个请求进行认证处理,包含了初始化、认证、质询、和禁止访问4个功能:

1
2
3
4
5
6
7
public interface IAuthenticationHandler
{
Task InitializeAsync(AuthenticationScheme scheme, HttpContext context);
Task<AuthenticateResult> AuthenticateAsync();
Task ChallengeAsync(AuthenticationProperties properties);
Task ForbidAsync(AuthenticationProperties properties);
}

AspNet/Security项目中,提供了一个抽象类AuthenticationHandler<TOptions>作为IAuthenticationHandler的基础实现:

1
2
3
4
5
public abstract class AuthenticationHandler<TOptions> : IAuthenticationHandler 
where TOptions : AuthenticationSchemeOptions, new()
{
// ...
}

其中,初始化只是简单地设置模式、HttpContext,创建事件处理器等:

Read More

Authentication — (2) 认证服务及其对HttpContext的认证扩展方法

此部分代码位于aspnet/HttpAbstractions项目中,命名空间为 Microsoft.AspNetCore.Authentication

认证服务及以其为基础对HttpContext的扩展方法是所有认证过程的核心:

  • 对于默认的认证模式,是直接转发调用HttpContext.Authenticate()扩展方法
  • 对于自定义的认证请求处理,需要手工在HandleRequestAsync()函数中,自行使用Context.SignInSync(signInScheme,principal,properties)进行登入

认证服务接口

IAuthentictionService接口规定认证、质询、禁止、登入、登出共五个功能:

1
2
3
4
5
6
7
8
9
10
11
12
public interface IAuthenticationService
{
Task<AuthenticateResult> AuthenticateAsync(HttpContext context, string scheme);

Task ChallengeAsync(HttpContext context, string scheme, AuthenticationProperties properties);

Task ForbidAsync(HttpContext context, string scheme, AuthenticationProperties properties);

Task SignInAsync(HttpContext context, string scheme, ClaimsPrincipal principal, AuthenticationProperties properties);

Task SignOutAsync(HttpContext context, string scheme, AuthenticationProperties properties);
}

而其默认实现AuthenticationService中包含了两个重要字段,即当前的认证模式集Schemes和认证处理器集Handlers(后面会单独讲解IClaimsTransformation Transform):

Read More

Authentication — (1) 基本概念

这部分代码在HttpAbstractions项目中定义,定义在Microsoft.AspNetCore.Authentication命名空间下。

本篇笔记分为两大部分:

  1. 第一部分主要讲述认证模式认证处理器及相应的认证模式Provider认证处理器Provider
  2. 第二部分主要对认证过程中涉及的相关类型进行描述,主要包括认证属性认证票据认证结果

认证模式

认证模式类AuthenticationScheme非常简单,顾名思义,它代表了某一种特定的认证模式,其中还包含了对应的认证处理器的类型信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class AuthenticationScheme
{
public AuthenticationScheme(string name, string displayName, Type handlerType)
{
if (name == null) { /* throw */ }
if (handlerType == null) { /* throw */ }
if (!typeof(IAuthenticationHandler).IsAssignableFrom(handlerType)) { /* throw */ }

Name = name;
HandlerType = handlerType;
DisplayName = displayName;
}

public string Name { get; }
public string DisplayName { get; }

public Type HandlerType { get; }
}

常见的认证模式有 CookiesBearerOAuthOpenIdConnectGoogleMicrosoftFacebook等。每种 认证模式都有各自的处理器负责处理用户认证事宜。注意,Scheme中存储的并非是Handler实例,而是Handler的类型!

Read More

Authentication — (0) 从Authentication中间件说起

ASP.NET Core认证相关的代码比较分散,主要涉及三个项目仓库:

  1. HTTP Abstractions项目,即以前的aspnet/HttpAbstractions仓库。在这个仓库中,包含了一些与认证相关的高层接口和与框架安全相关的核心概念。
  2. Security项目,即之前的aspnet/Security仓库。这个仓库中定义了与认证相关的一些基本实现,并内置了一些常见的认证模式、认证处理器。
  3. Identity项目,即之前的aspnet/Identity。这个仓库是ASP.NET Core Identity框架的实现。本系列的笔记不会对其做过多的源码分析,这里只作为一种认证模式介绍。

去年8月份,我在阅读ASP.NET Core认证相关的源码后,陆陆续续在有道云笔记中记录了8篇笔记(系列):

  1. Authentication — (0) 从Authentication中间件说起
  2. Authentication — (1) 基本概念.md
  3. Authentication — (2) 认证服务及其对HttpContext的认证扩展方法
  4. Authentication — (3.1) 认证处理器的实现之AuthenticationHandler抽象基类
  5. Authentication — (3.2) 认证处理器的实现之JwtBearerHandler
  6. Authentication — (3.3) 认证处理器的实现之RemoteAuthenticationHandler
  7. Authentication — (4) Authentication服务的配置与构建
  8. Authentication — (5) 如何自定义认证处理器

(一点题外话:最近这大半年,我几乎都只在有道云上记录笔记(PC端+App+网页版)。不过最近我在修改笔记的过程中发现,有道云笔记网页版经常会发生笔记相互覆盖的情况。最恼火的是,一旦修改了标题,连历史记录也一并丢失了!这种情况已经出现我身上数次了。基于此,我以后记笔记的策略是:有道云做初稿,随时记录想法;整理完成后提交公众号发布,由于公众号有良好的CDN网络,供平时阅读和温习;最后使用个人网站作为终极备份

随着ASP.NET Core的发展,之前的三个仓库在去年已经被存档,目前新的项目都位于ASP.NET Core中心仓库下。为了表述方便,这个系列的源码分析文章对老仓库和新项目位置不做过多区分。

中间件AuthenticationMiddleware是理解ASP.NET Core认证的入口,尽管搞懂这块需要很多基本知识,作为总纲式的知识,我还是选择把它作为第一篇讲述。

Read More

BotBuilder 源码通读6 —— 对话建模与实现2

上一篇关于BotBuilder对话建模的博文中讲述了对话建模的一些基本思想和工作原理。这一篇作为补充,记录几个具体的对话类作用。

Prompt

Prompt<T>类对话是最为基础的一类Dialog。这类Dialog无非就是询问用户、提示用户输入,并对用户输入的结果予以检验。比如Prompt<T>::ContinueDialogAsync()方法实现为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public override async Task<DialogTurnResult> ContinueDialogAsync(DialogContext dc, CancellationToken cancellationToken = default(CancellationToken))
{
if (dc == null) { /* throw */ }
// Don't do anything for non-message activities
if (dc.Context.Activity.Type != ActivityTypes.Message) { return EndOfTurn; }

// Perform base recognition
var instance = dc.ActiveDialog;
var state = (IDictionary<string, object>)instance.State[PersistedState];
var options = (PromptOptions)instance.State[PersistedOptions];

//识别用户输入
var recognized = await OnRecognizeAsync(dc.Context, state, options, cancellationToken).ConfigureAwait(false);
// 记录所尝试的次数
state[AttemptCountKey] = Convert.ToInt32(state[AttemptCountKey]) + 1;

// 验证是否有效
// ... set `isValid` by : `_validator(promptContext,cancellationToken)`

// 结束对话并恢复上一级对话执行
if (isValid) { return await dc.EndDialogAsync(recognized.Value, cancellationToken).ConfigureAwait(false); }

if (!dc.Context.Responded) { await OnPromptAsync(dc.Context, state, options, true, cancellationToken).ConfigureAwait(false); }
return EndOfTurn;
}

从上面的代码可以看出,Prompt<T>ContinueDialogAsync()方法需要子类提供OnPromptAsync()方法和OnRecognizeAsync()的实现,以负责提示用户输入、及识别用户输入。:

1
2
3
protected abstract Task OnPromptAsync(ITurnContext turnContext, IDictionary<string, object> state, PromptOptions options, bool isRetry, CancellationToken cancellationToken = default(CancellationToken));

protected abstract Task<PromptRecognizerResult<T>> OnRecognizeAsync(ITurnContext turnContext, IDictionary<string, object> state, PromptOptions options, CancellationToken cancellationToken = default(CancellationToken));

Prompt<T>有不同的子类实现,最常见的莫过于TextPrompt,它接受一个string类型作为输入。其他常见的Prompt<>子类包括:NumberPrompt<T>DateTimePromptConfrimPromptChoicePromptAttachmentPrompt等。此外,还有一些极个别的对话类,名字中带有Prompt,但并非继承自Prompt<T>,比如OAuthPrompt类,此处不予赘述。

Read More

机器学习 KNN算法

KNN算法的基本思想

考虑一个平面上有数百个样点,这些样点分属于A类、B类和C类。已知各个点的类别与其坐标存在某种密切的关联,特定类别的点会在某个区域富集。现有一个新的点,已知该点周边最近的$K$个点中,几乎都属于A类,问该点最有可能是哪个类?

常识告诉我们,该点很可能也属于A类。这便是KNN的基本思想——最邻近的$K$个点中,如果其中大部分点都属于某个类别,那么该点就很可能也属于该类。

KNN算法的实现

KNN算法非常简单:

  1. 计算该点与所有样点之间的距离(这里采用欧几里得距离)
  2. 找出最近的k个样品
  3. 统计这k个样品的类别

对于点$\bold x^{(p)}$ 和点$\bold x^{(q)}$之间的欧几里得距离,其实就是各个分量之差的平方和再取平方根。令:

$\bold x^{(p)}=(x^{(p)}_1,x^{(p)}_2,x^{(p)}_3,...,x^{(p)}_n)$
$\bold x^{(q)}=(x^{(q)}_1,x^{(q)}_2,x^{(q)}_3,...,x^{(q)}_n)$,

欧几里得距离可以表示为:

1
2
%% KaTex
\text{Distance}^2 =\displaystyle \sum_{t=1}^{n} (x^{(p)}_t - x^{(q)}_t)^{2}

不过,这里我们并不需要取平方根,因为二者呈正相关,我们只需要计算各个分量之差的平方和即可得到最近的$k$个样品点。用代码表示则是:

1
2
3
4
5
/// Euclidean distance function 
let distance (values1: float list) (values2: float list) =
List.sumBy
(fun it -> Math.Pow( float( (fst it) - (snd it)), 2.0))
(List.zip values1 values2)

根据前面描述的算法,可以给出KNN的算法完整实现如下(由于最近在学习F#,所以这里使用F#):

Read More

BotBuilder 源码通读5 —— 对话建模与实现1

Dialog

BotBuilder中的Dialog变迁历史是相当复杂的。v4版本之初,曾有过IDialog相关接口;后来出于一些原因,官方删除了这个接口;而在较新的ComposableDialog分支中,官方又引入了一个新的IDialog接口。本篇笔记以目前的v4.1版(stable)作为基础,并不涉及新的IDialog接口。

v4.1版本中的Dialog 是所有 dialog 的基类,大致公开以下几个属性和方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
+-----------------------------------------+
| |
| Dialog |
| |
+-----------------------------------------+
| Id |
+-----------------------------------------+
| BeginDialogAsync(dc,opt,ct) |
| ContinueDialogAsync(dc,ct) |
| ResumeDialogAsync(dc,reason,result,ct) |
| RepromptDialogAsync(tc,instance,ct) |
| EndDialogAsync(tc,instance,reason,ct) |
+-----------------------------------------+

从效果上讲,Dialog是对“对话”的抽象:一个Dialog便是一次对话;在一个对话进行期间,可以开启其他子对话,待子对话完成后会“恢复”父对话的处理。这个过程如同栈一样。
可以把Dialog类比作一个函数:定义一个Dialog类似于定义某种函数;Dialog的执行过程类似于函数的调用过程,Dialog的嵌套执行也类似于函数的嵌套调用。

Read More

BotBuilder 源码通读4 —— 状态管理之状态属性访问器

上文说到,BotState基类提供了几个若干方法来管理缓存和持久层。但是这些方法使用起来并不方便。
比如调用GetPropertyValueAsync<T>(ctx,prop)方法,用户需要关心缓存是否已经加载到ITurnContext.TurnState[prop]中,同样的情况也会发生于DeletePropertyValueAsync(ctx, prop)SetPropertyValueAsync(ctx,prop)ClearStateAsync(ctx)方法上。

每次都通过BotState来load状态、然后尝试根据一个字符串来读取/删除/设置某个PropertyValue,这种方式对于开发者而言过于啰嗦,于是,BotState还提供了一个方法来简化我们的代码——IStatePropertyAccessor<T>:状态属性访问器——用于对内存中的状态属性进行存取操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public abstract class BotState : IPropertyManager
{
// ...

public IStatePropertyAccessor<T> CreateProperty<T>(string name)
{
if (string.IsNullOrWhiteSpace(name))
{
throw new ArgumentNullException(nameof(name));
}

return new BotStatePropertyAccessor<T>(this, name);
}
}

BotState可以通过CreateProperty<T>(string name)方法返回一个IStatePropertyAccessor<T>对象。这里,IStatePropertyAccessor<T>代表了可以对状态属性进行读、写、删的访问器:

Read More

BotBuilder 源码通读3 —— 状态管理之 BotState

BotState

对于Bot而言,根据作用域的不同,可以分为三大块:

  • UserState : 特定Channel下的特定用户的状态。持久化时的键名为{ChannelId}/users/{UserId}
  • ConversationState:特定Channel下的特定会话的状态。持久化时的键名为{ChannelId}/conversations/{ConversationId
  • PrivateConversationState:特定Channel下,特定会话的特定用户的状态。持久化时的键名为{ChannelId}/conversations/{ConversationId}/users/{UserId}

这三个类都继承自抽象类BotState

1
2
3
4
5
6
7
8
9
10
11
12
13
14
                             +----------------+
| |
| BotState |
| |
+-------^-^-^----+
| | |
| | |
+------------------+ | +------------------+
| | |
+-----------------+---+ +-----------+-+ +-------------+--------------+
| | | | | |
| ConversationState | | UserState | | PrivateConversationState |
| | | | | |
+---------------------+ +-------------+ +----------------------------+

本质上,BotState并不是状态,而是状态管理器。状态可以被抽象为一个IDictionary<string,object>型对象,而状态管理器是可以对某个状态进行存、取、删的管理器,更具体的,状态管理器还可以对某个状态的某个属性进行存、取、删操作。

  • 状态是IDictionary<string,object>型对象,一个程序可以包含有任意多种类型的状态,常见的状态分类会话状态用户状态、和私有会话状态
  • 状态属性是某个状态下的一个属性,还是一个IDictionary<string,object>类型的对象,一个状态可以包含任意多的属性。
  • BotState及其实现ConversationState类、UserState类等均是针对某种具体状态的管理器。其中嵌入了一个IStorage对象,在此基础之上,状态管理器可以从持久层加载特定的状态、可以删除特定的状态,还可以保存特定的状态到持久层。比如ConversationState是针对会话状态的管理器,负责对会话状态及会话状态的属性进行存、取、删;而UserState则是针对用户状态的管理器,负责对用户状态及用户状态的属性进行存、取、删;
  • 状态属性访问器是隐含了

Read More

MQTT 协议与实现 1 —— 协议概要

MQTT 协议是连接物与物(M2M)的协议,该协议规定了一系列用于物与Broker之间相互交换的控制报文。总体来说,一个控制报文结构分为三个部分:

  1. 固定头(Fixed Header) : 所有 MQTT Control Packets 的固定存在的报头
  2. 可选头(Variable Header) : 某些 MQTT Control Packets 会包含的报头
  3. 负载(Payload) : 某些 MQTT Control Packets 会有传输 Payload

其中,首字节的前四个比特规定了共$2^4=16$种可能的控制报文类型,后4个字节则是一些标志位组合,可以在一定程度上视作为对报文类型的补充。MQTT报文的基本格式为:

1
2
%% KaTex
\overbrace{\color{orange}\text{报文类型}}^{\text{4 bits}} \enspace \overbrace{\color{green}\text{标志位}}^{\text{4 bits}} \enspace \overbrace{\color{blue} \text{剩余长度} }^{\text{1..4 Bytes}} \enspace \overbrace{\color{yellow} \text{可变头}}^{\text{...}} \enspace \overbrace{\color{skyblue} \text{Payload}}^{\text{...}}

固定头

固定头的由两部分组成:首字节及剩余长度。正如上文所述,首字节的前四个比特位代表了不同的控制报文类型。从功能上说,控制报文主要分为以下两大类:

Read More