前言

这篇文章我想带领大家了解一下 ASP.NET Core 中如何进行的身份验证,在开始之前强烈建议还没看过我写的 Identity 系列文章的同学先看一下。

Identity 入门系列文章:

名词解释

做 Web 开发的都知道 HTTP 协议是无状态的,那么服务端如果想知道此次请求的用户是哪个登录的用户,那么就需要有一种标识每次都被传递到服务端,那么这个标识就是我们都知道的 Cookie(这里我们先不考虑header中携带标识的情况),服务端根据 Cookie 中携带的信息进行识别的一个过程就是身份验证,所有基于 WEB 的服务端都是如此,无关乎语言和框架。

在整个身份验证的过程中,又分为两个部分即认证和授权,很多同学区分不出来这两个东西,因为这两个单词看起来有点像,导致经常认错,这里我教大家一个小方法,就是记住他们的发音,使用某种方法让发音和汉字对应起来,这样就记住了。

Authentication [ɔ:,θenti'keiʃən] 认证

Authorization [,ɔ:θərai'zeiʃən, -ri'z-] 授权

分享一下我的方法,认证的拼音是(renzheng),其中 zheng 包含 en ,同样的 Authentication 也包含 en,这样我就记住了这个单词是认证,那么另外一个就是授权了。

认证:确定用户身份的一个过程。 注意是一个过程。

授权:确认用户可以做哪些事情,即权限。

基于 Claims 的身份

在 ASP.NET Core 中主要是使用的基于 Claims 的身份验证,也就是说将用户的属性都抽象成证件单元来表示了,通过证件单元来表示一张身份证。

我们先来回顾一下如何制造一张身份证:

//证件单元
var claims = new List<Claim>()
{
new Claim(ClaimTypes.Name,"奥巴马"),
new Claim(ClaimTypes.NameIdentifier,"身份证号")
}; //使用证件单元创建一张身份证
var identity = new ClaimsIdentity(claims, "AuthenticationTypeXXX");

注意,在 new ClaimsIdentity 的时候第二个参数是 AuthenticationType,我在前面文章中讲过这个是 载体类型,也就是实体形式的身份证,对吧?

那么,在使用程序创建一个身份的时候,需要就指定这个载体了,在HTTP验证中,我们将载体设置为Cookies,代码如下:

var cookie身份证 = new ClaimsIdentity(claims, "Cookies");

有了Cookie身份证,我们还需要一个携带者,看过之前文章的可能知道,我讲 ClaimsPrincipal 的时候,一张身份证就不是代表一个人了,而是不通的身份种类,比如你可以同时是一名教师,母亲,商人。如果你想证明你同时有这几种身份的时候,你可能需要出示教师证,你孩子的出生证,法人代表的营业执照证。

所以,我们还需要制造一个人,这个人来携带各种证件,我们就携带上一步制造的 cookie身份证 吧,先携带这一个好了:

var 人 = new ClaimsPrincipal(cookie身份证)

我们来看一下完整的一个代码

//证件单元
var claims = new List<Claim>()
{
new Claim(ClaimTypes.Name,"奥巴马"),
new Claim(ClaimTypes.NameIdentifier,"身份证号")
}; //使用证件单元创建一张cookie身份证
var cookie身份证 = new ClaimsIdentity(claims, "Cookies"); //创建一个人携带cookie身份证
var 人 = new ClaimsPrincipal(cookie身份证)

多重身份

当一个人有多种身份的时候,这个时候可能有人会问,什么情况下会有多种身份呢?

举个简单的例子,上面的 cookie身份证 算是一种身份,那么我可能还有比如接入 OAuth的时候使用的 bearer身份证,接入第三方登录时候使用过的 google身份证facebook身份证microsoft身份证 等等,这就叫多重身份

多种身份种的每一种身份都有一个 AuthenticationType 对应一个认证方式,后面我会讲到。

以上,我们理清楚了一个重要的逻辑关系就是:

一个人有多种身份,每个身份都有证件单元和一个认证方式组成。

接下来,你们可能就会认为我就开始介绍认证和授权了。 不,很多东西有时候和你想象的并不一样,比如这篇文章也是,所以接下来我要讲的东西是 IdentityModel

IdentityModel

IdentityModel 是一种基于 Claim 的 Identity 库,它提供了一组类用来标识用户身份,以及对这些东西的抽象。

有些同学可能会问,不是已经有 ClaimsIdentity 来表示用户身份了吗?为啥又还有其他的表示用户身份的东西呢?

大哥,身份认证是一整套复杂的东西,包含很多组件,协议,标准,如果很简单就学会了我还用得着写文章教你吗? 还是接着介绍吧。

最初,IdentityModel 是属于 WIF(Windows Identity Foundation) 的一部分,WIF 是微软2004年给 .NET 平台搞的一套身份验证框架(包含Claims,Configuration,Metadata,Policy,Servicesd等等),微软想把这个东西作为 .NET 标准框架的一部分,所以它的命名空间是 System.IdentityModel, 了解这个东西的人不是很多,不过不知道也没关系,反正这玩意也已经被淘汰了。

在 .NET Core 中, WIF 这些套件只有 System.IdentityModel.Tokens.Jwt 被保留了下来,其他全被扔掉了,为什么呢?

原因是只有 JWT 这部分东西有用,其他的部分更多的是为以前的 Web Servics, WCF 那套分布式东西设计的,那套分布式的东西淘汰了,自然也不必要保留了。

在没有 .NET Core 的时候,我们想实现一套标准的单点登录(SSO)系统就可以利用 System.IdentityModel 因为它已经为我们做了大量工作,并且是标准化的。在 .NET Core 中也需要一些标准的抽象东西那怎么办呢?

微软弄了一套新的 IdentityModel 的库,命名空间为 Microsoft.IdentityModel。很多人甚至都找不到它的源码在哪里,我一开始也没找到,最后发现在 https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet 这个仓库里面。

这个库的组成部分同样都是抽象的部分,包括相关的协议对象,票据的加解密,票据存储 等等,也就是说微软给 .NET Core 的身份验证体系又定义了一套抽象的东西,任何第三方基于身份验证的实现库或者框架都要遵循(依赖)他们。

以上的关于 IdentityModel 的介绍和下面我要将的东西关系不是很大,之所以要在这里引入是因为我要为后续的文章做铺垫,在这里引入最合适不过。

接下来我们继续讲解,就开始了认证部分的讲解。

Authentication 认证

我之前讲过奥巴马去杭州旅游的故事,有些同学反映还是看不懂,所以我决定这次配合 ASP.NET Core 中 Cookie 身份认证的过程来讲解。

再次声明,如果你还没看过 Identity 入门一 这篇文章,我要求你先跳过去看一下,因为接下来的内容是这篇文章的延申。

我们假设你现在已经知道了身份证,然后现在使用身份证是坐火车。

就是奥巴马

身份证就是 cookie身份证

我们将开始我们的认证旅程,同时结合我们最熟悉的 HTTP 登录流程。

奥马巴要去乘坐火车,那么现在他要过安检,在Web登录中就是对应的登录,登录要使用用户名密码,但是用户名密码是属于业务逻辑方面的验证,我们不考虑,因为假设是第三方登录就不需要输入用户名和密码了,所以你可以理解为我们假设用户名和密码都正确,现在奥马巴要过安检了。

对应的代码为:

//证件单元
var claims = new List<Claim>()
{
new Claim(ClaimTypes.Name,"奥巴马"),
new Claim(ClaimTypes.NameIdentifier,"身份证号")
}; //使用证件单元创建一张身份证
var identity = new ClaimsIdentity(claims,"Cookies"); //使用身份证创建一个证件当事人,也就是奥巴马
var identityPrincipal = new ClaimsPrincipal(identity); //奥巴马开始过安检
await HttpContext.SignInAsync("Cookies", identityPrincipal);

现在,我们来运行程序,看看会发生什么。你先不用管 HttpContext.SignInAsync 是做什么用的,下面会说。

新建一个ASP.NET Core 空的 MVC 程序,然后在登录的 Action 方法中粘贴以上代码,然后按 F5 运行。

出错了,根据错误信息我们可以看出是因为我们没有注册身份验证的中间件,而且错误已经告诉了我们应该怎么做,我们尝试解决这个错误。

Startup.cs 文件中 ConfigureServices 方法注册服务

public void ConfigureServices(IServiceCollection services)
{
... services.AddAuthentication("Cookies")
.AddCookie("Cookies"); ...
}

注意,AddAuthentication 这里是指定默认的认证载体类型,AddCookie 这里是注册载体类型的处理程序。

认证部分我会在下一篇中详细介绍,所以这里先大致了解下。

再次 F5 运行发现已经正常了。

我们打开浏览器的 Cookie 查看一下,可以看到多了一项 Cookie 记录

我们可以看到这个 Cookie 的 Name 为 .AdpNetCore.Cookie,Value 为一大长串加密的字符串。

流程讲解

现在我来开始讲 HttpContext.SignIn

它是一个扩展方法,最终是调用的 IAuthenticationService 接口的 SignInAsync 方法。我们来看下接口的定义:

public interface IAuthenticationService
{
Task SignInAsync(HttpContext context, string scheme, ClaimsPrincipal principal, AuthenticationProperties properties);
}

有了接口,肯定有实现咯。 我们找一下实现在哪里,很容易,根据 ASP.NET Core 的 IOC 来找就行了,很明显在 AddCookie 这个扩展里面。

public void ConfigureServices(IServiceCollection services)
{
... services.AddAuthentication()
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme);
↑↑↑ 实现就在这里
...
}

我们找到了处理类 CookieAuthenticationHandler 这个对象,我们再来看具体的代码。

protected async override Task HandleSignInAsync(ClaimsPrincipal user, AuthenticationProperties properties)
{
// Process the request cookie to initialize members like _sessionKey.
await EnsureCookieTicket();
var cookieOptions = BuildCookieOptions(); var signInContext = new CookieSigningInContext(
Context,
Scheme,
Options,
user,
properties,
cookieOptions); await Events.SigningIn(signInContext); var ticket = new AuthenticationTicket(signInContext.Principal, signInContext.Properties, signInContext.Scheme.Name); if (Options.SessionStore != null)
{
if (_sessionKey != null)
{
await Options.SessionStore.RemoveAsync(_sessionKey);
}
_sessionKey = await Options.SessionStore.StoreAsync(ticket);
var principal = new ClaimsPrincipal(
new ClaimsIdentity(
new[] { new Claim(SessionIdClaim, _sessionKey, ClaimValueTypes.String, Options.ClaimsIssuer) },
Options.ClaimsIssuer));
ticket = new AuthenticationTicket(principal, null, Scheme.Name);
} var cookieValue = Options.TicketDataFormat.Protect(ticket, GetTlsTokenBinding()); Options.CookieManager.AppendResponseCookie(
Context,
Options.Cookie.Name,
cookieValue,
signInContext.CookieOptions); var signedInContext = new CookieSignedInContext(
Context,
Scheme,
signInContext.Principal,
signInContext.Properties,
Options); await Events.SignedIn(signedInContext); // Only redirect on the login path
var shouldRedirect = Options.LoginPath.HasValue && OriginalPath == Options.LoginPath;
await ApplyHeaders(shouldRedirect, signedInContext.Properties); Logger.SignedIn(Scheme.Name);
}

大概步骤分为:

1、创建一个SignIn Cookie 上下文对象

2、将上下文对象转换为票据(Ticket),转换为票据的目的是为了加密

3、将票据进行加密

4、将加密后的票据写入Cookie

很有意思的是第三步,我需要展开来说下,这也结束。

在第三步加密票据的过程中可以看到有一个 if 判断 if (Options.SessionStore != null),是做什么用的呢?

可能有些同学会有疑问,我们基于Claim的Cookie存储假如我的证件单元很多,就会生成一个非常大的cookie,每次传输是有性能影响的,并且Cookie是有最大限制的,怎么办呢?

其实解决办法就是我们就可以开启这个 SessionStore,将Cookie存储在服务端例如Redis等缓存中。代码如下:

services.AddSingleton<ITicketStore, MyRedisTicketStore>();

services.AddOptions<CookieAuthenticationOptions>("Cookies")
.Configure<ITicketStore>((o, t) => o.SessionStore = t);

现在,浏览器中已经存储了用户的身份啦。

以上就是确认用户身份的一个过程,在这个过程中我们使用Cookie来标记用户身份并且存储到浏览器的Cookie了,这个过程就是 认证

其实上面就是 ASP.NET Core 中的 Forms 身份验证中的认证阶段。

扩展阅读

在不使用Cookie的时候怎么确定身份呢? 比如在 WEB API 接口中使用的就是 Access Token,这也相当于Cookie中的票据了,那么在 WEB API 中如何确定身份,流程又是怎么样的呢?可以看后续文章。

总结

才把认证写完发现已经这么长了,下篇再来讲讲授权吧。

如果你对 .NET Core 有兴趣的话可以关注我,我会定期的在博客分享我的学习心得。


本文地址:http://www.cnblogs.com/savorboard/p/authentication.html

作者博客:Savorboard

本文原创授权为:署名 - 非商业性使用 - 禁止演绎,协议普通文本 | 协议法律文本

ASP.NET Core 身份验证(一)的更多相关文章

  1. ASP.NET Core身份验证

    asp.net core 身份验证 本文旨在演示如果使用内置的 identity 实现 asp.net core 的身份验证,不会进行其它扩展.本文将通过最简单的代码演示如何进行登录和身份验证操作. ...

  2. ASP.NET Core身份验证服务框架IdentityServer4-整体介绍

    一.整体情况 现代应用程序看起来更像这个: 最常见的相互作用: 浏览器与Web应用程序的通信 Browser -> Web App Web应用程序与Web API通信 基于浏览器的应用程序与We ...

  3. ASP.NET Core身份识别

    Introduction to Identity 66 of 93 people found this helpful By Pranav Rastogi, Rick Anderson, Tom Dy ...

  4. ASP.NET Core Identity 验证特性 - ASP.NET Core 基础教程 - 简单教程,简单编程

    原文:ASP.NET Core Identity 验证特性 - ASP.NET Core 基础教程 - 简单教程,简单编程 ASP.NET Core Identity 验证特性 上一章节我们简单介绍了 ...

  5. 深入解读 ASP.NET Core 身份认证过程

    长话短说:上文我们讲了 ASP.NET Core 基于声明的访问控制到底是什么鬼? 今天我们乘胜追击:聊一聊ASP.NET Core 中的身份验证. 身份验证是确定用户身份的过程. 授权是确定用户是否 ...

  6. ASP.NET Forms 身份验证

    ASP.NET Forms 身份验证 在开发过程中,我们需要做的事情包括: 1. 在 web.config 中设置 Forms 身份验证相关参数.2. 创建登录页. 登录页中的操作包括: 1. 验证用 ...

  7. Asp.net Mvc 身份验证、异常处理、权限验证(拦截器)实现代码

    本问主要介绍asp.net的身份验证机制及asp.net MVC拦截器在项目中的运用.现在让我们来模拟一个简单的流程:用户登录>权限验证>异常处理 1.用户登录 验证用户是否登录成功步骤直 ...

  8. ASP.NET MVC 随想录——探索ASP.NET Identity 身份验证和基于角色的授权,中级篇

    在前一篇文章中,我介绍了ASP.NET Identity 基本API的运用并创建了若干用户账号.那么在本篇文章中,我将继续ASP.NET Identity 之旅,向您展示如何运用ASP.NET Ide ...

  9. ASP.NET中身份验证

    ASP.NET中身份验证有三种方式:Windows.Forms和Passport. 1.Windows验证,基于窗体验证,需要每个页面写上验证身份代码,相对灵活,但操作过于复杂: 2.Passport ...

随机推荐

  1. subline常用快捷键

    一次创建5个class为main的div  :    div.main*5 +TAB 快速生成HTML结构: ! + TAB 使盒子内的文本水平垂直方向对齐: height:value; line-h ...

  2. XamarinForm Effects 调用事件

    原文地址 在Xamarin.Forms控件中实现底层多点触控跟踪. 一个effect可以定义和调用一个事件,在底层本地视图中发出信号的变化.这篇文章演示如何实现底层多点触控跟踪,以及如何生成信号触摸活 ...

  3. 《Redis入门指南》第2版 读书笔记

    读第二遍了,感觉和几年前读时的收获不一样了.送上门来当树洞的独自承担一切 Redis以简洁为美Redis通信协议是Redis客户端与Redis之间交流的语言,通信协议规定了命令和返回值的格式.Redi ...

  4. MQTT, XMPP, WebSockets还是AMQP?泛谈实时通信协议选型 good

    Wolfram Hempel 是 deepstreamIO 的联合创始人.deepstreamIO 是一家位于德国的技术创业公司,为移动客户端.及物联网设备提供高性能.安全和可扩展的实时通信服务.文本 ...

  5. Python replace()方法

    描述 Python replace() 方法把字符串中的 old(旧字符串) 替换成 new(新字符串),如果指定第三个参数max,则替换不超过 max 次. 语法 replace()方法语法: st ...

  6. LNMP单点服务器搭建

    一.部署服务器环境 Linux:centos6.5 nginx:1.14.0 mysql:5.6.33 php:5.6.36 1.网络配置 2.FQDN /etc/hosts /etc/sysconf ...

  7. level.go

    package blog4go import ( "fmt" "strings" ) // LevelType type defined for logging ...

  8. JSON 数据重复 出现$ref

    JSONArray  类型  如果我们往里面add数据的时候 如果数据相同,那么就会被替换成 $ref:   也就是被简化了 因为数据一样所直接 指向上一条数据 循环引用:当一个对象包含另一个对象时, ...

  9. linux重置密码的方法和用户切换

    由于好久没有用本地的vmware了,本地虚拟机的密码都忘光了,这个不常遇见的问题碰到了,百度了一下解决了 学习源头:https://www.cnblogs.com/lippor/p/5537931.h ...

  10. mybatis 异常Result Maps collection does not contain value for java.lang.String

    Result Maps collection does not contain value for java.lang.String 以上是我报的错. 只要报Result Maps collectio ...