MVC5 - ASP.NET Identity登陆原理 - Claims-based认证和OWIN

MVC5 - ASP.NET Identity登陆原理 - Claims-based认证和OWIN

2014-04-01 08:27 by Jesse Liu, 6475 阅读, 46 评论, 收藏, 编辑

Membership系列的最后一篇引入了ASP.NET Identity,看到你们对它仍是挺感兴趣的,因而来一篇详解登陆原理的文章。本文会涉及到Claims-based(基于声明)的认证,咱们会详细介绍什么是Claims-based认证,它与传统认证方式的区别,以及它的特色。同时咱们还会介绍OWIN (Open Web Interface for .NET) 它主要定义了Web Server 和Web Application之间的一些行为,而后实现这两个组件的解耦(固然远不止这么点东西,我相信OWIN立刻就会掀起一场血雨腥风)ASP.NET Identity是如何利用OWin实现登陆的,都是干货,同窗,你准备好学习了么? html

目录

ASP.NET Identity登陆原理

废话少说,咱们直接切入正题。在上一篇从Membership到ASP.NET Identity,咱们已经给了一个简单的实例,而且大体的描述了一下ASP.NET Identity的结构体系,可是ASP.NET Identity主要提供的功能是帮助咱们管理用户,角色等信息,它主要负责的是存储这一块,也就是咱们的信息存到哪里去的问题。可是用户是如何实现登陆的? 是Forms认证么?用到Cookie了么Cookie里面有保存明文信息么(咳咳,最近某程旅游网好像很火?),接下来咱们就来一一的回答这些问题。web

在上一篇的例子中,咱们能够简单的发现,要实现登陆实际上只有简单的三行代码数据库

1缓存

2安全

3服务器

4cookie

5数据结构

6mvc

7app

8

9

10

11

12

13

14

private IAuthenticationManager AuthenticationManager

{

get { return HttpContext.GetOwinContext().Authentication; }

}

private async Task SignInAsync()

{

// 1. 利用ASP.NET Identity获取用户对象

var user = await UserManager.FindAsync("UserName", "Password");

// 2. 利用ASP.NET Identity获取<a target="_blank" style="color: #0000F0; display:inline; position:static; background:none;" href="http://www.so.com/s?q=identity&ie=utf-8&src=se_lighten_f">identity</a> 对象

var identity = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);

// 3. 将上面拿到的identity对象登陆

AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = true }, identity);

}

咱们发现UserManager.CreateIdentityAsync返回给咱们的对象是一个ClaimsIdentity,这又是一个什么玩意?它和咱们原来所熟知的Identity对象有什么关联么?毕竟长的那么像,在深刻以前,咱们先要了解一下下面的概念。

什么是Claims-based(基于声明)的认证

首先这个玩意不是微软特有的,Claims-based认证和受权在国外被普遍使用,包括微软的ADFS,Google,Facebook等。 国内我就不知道了,没有使用过国内的第三方登陆,有集成过QQ登陆或者支付宝登陆的同窗能够解释一下。

Claims-based认证主要解决的问题?

对比咱们传统的Windows认证和Forms认证,claims-based认证这种方式将认证和受权与登陆代码分开,将认证和受权拆分红另外的web服务。活生生的例子就是咱们的qq集成登陆,未必qq集成登陆采用的是claims-based认证这种模式,可是这种场景,千真万确就很是适合claims-based认证。

Claims-based认证的主要特色:

  • 将认证与受权拆分红独立的服务
  • 服务调用者(通常是网站),不须要关注你如何去认证,你用Windows认证也好,用令牌手机短信也好,与我无关。
  • 若是用户成功登陆的话,认证服务(假如是QQ) 会返回给咱们一个令牌。
  • 令牌当中包含了服务调用者所须要的信息,用户名,以及角色信息等等。

总的来讲就是,我不再用管你怎么登陆,怎么样去拿你有哪些角色了,我只须要把你跳到那个登陆站点上,而后它返回给我令牌信息,我从令牌上获取须要的信息来肯定你是谁,你拥有什么角色就能够了。

进一步理解Claims-based 认证

为了让你们进一步理解Claims-based认证,咱们从一个普通的登陆场景开始提及,拿QQ集成登陆来举例。

  1. 用户跑到咱们的网站来访问一个须要登陆的页面
  2. 咱们的网站检测到用户没有登陆,返回一个跳转到QQ登陆页的响应(302 指向QQ登陆页面的地址并加上一个返回的连接页面,一般是returnUrl=)
  3. 用户被跳转到指定QQ的登陆页面
  4. 用户在QQ登陆页面上输入用户名和密码,QQ会到本身的数据库中查询,一旦登陆成功,会返回一个跳转到咱们站点的响应(302指向咱们的网站页面)
  5. 用户被跳转到咱们网站的一个检测登陆的页面,咱们能够拿到用户的身份信息,创建ClaimsPrinpical和ClaimsIdentity对象,生成cookie等。
  6. 咱们再把用户带到指定的页面,也就是returnUrl,那是用户登陆前最后一次访问的页面

简单的来讲,就是把登陆的代码(验证用户,获取用户信息)拆分红独立的服务或组件。

ASP.NET 下的 Claims-based认证明现

说完什么是Claims-based认证以后,咱们接下来就能够看看ClaimsIdentity以及ClaimsPrincipal这两个类,他们是.NET下Claims-based认证的主要基石。固然正如咱们所想,他们继承了接口IIdentity和IPrincipal。

IIdentity封装用户信息

这个接口很简单,它只包含了三个最基本的用户身份信息。

IPrincipal 表明着一个安全上下文

这个安全上下文对象包含了上面的identity以及一些角色和组的信息,每个线程都会关联一个Principal的对象,可是这个对象是属性进程或者AppDomain级别的。ASP.NET自带的 RoleProvider就是基于这个对象来实现的。

CalimsIdentity和ClaimsPrincipal

在System.Security.Claims命名空间下去,咱们能够发现这两个对象。下面咱们来作一个小例子,这个小例子会告诉咱们这两个对象是如何进行认证和受权的。咱们要作的demo很简单,建一个空的mvc站点,而后加上一个HomeController,和两个Action。而且给这个HomeController打上Authroize的标签,可是注意咱们没有任何登陆的代码,只有这个什么也没有的Controller和两个什么也没有的Action。

固然,结果也是可想而知的,咱们获得了401页面,由于咱们没有登陆。

下面咱们就来实现登陆,这里的登陆很是简单,咱们手动去建立这个ClaimsIdentity和ClaimsPrincipal对象,而后将Principal对象指给当前的HttpContext.Current.User。

咱们在Global.asax中添加了Application_AuthenticateRequest方法,也就是每次MVC要对用户进行认证的时候都会进到咱们这个方法里面,而后咱们就这样神奇的把用户给登陆了。

固然,咱们没有Home/Manager的访问权限,由于咱们上面只给了用户Users的Role。

如今你们知道ClaimsIdentity和ClaimsPrincipal是如何使用了么?这里要注意一下的是,咱们没有设置IsAutheiticated为true,在.NET4.5之前,对于GenericIdentity只要设置它的Name的时候IsAutheiticated就自动设置为true了,而对于ClaimsIdentity是在它有了第一个Claim的时候。在.NET4.5之后,咱们就能够灵活控制了,默认ClaimsIdentity的IsAutheiticated是false,只有当咱们构造函数中指定Authentication Type,它才为true。

最后结论,咱们讲了ClaimsIdentity什么的,讲了这么多和今天的主题有嘛关系?咱们上面说ASPNET Identity登陆有三句话,第一句话能够略过,第二句话就是咱们上面讲的。

1

var identity = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);

UserManager实际上只是为咱们建立了一个ClaimsIdentity的对象,仍是经过咱们本身从数据库里面取出来的对象来建立的,它也就干了那么点事,一层小小的封装而已。不要被后面的DefaultAuthenticationTypes.ApplicationCookie吓到了,这里尚未和cookie扯上半点关系,这就是一个字符串常量,和咱们上面本身定义的MyClaimsLogin是没有区别的。

到这里,我想算是把登陆代码的第二句话讲完了,讲清楚了,那么咱们来看看第三句话,也就是最后一句,其实它才是登陆的核心,第二句只是建立了一个ClaimsIdentity的对象。

1

2

3

4

5

private IAuthenticationManager AuthenticationManager

{

get { return HttpContext.GetOwinContext().Authentication; }

}

AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = true }, identity);

经过F12查看,发现IAuthenticationManager 在  Microsoft.Owin.Security命名空间下,而这个接口是定义在Microsoft.OWin.dll中的。这又是个什么玩意儿?带着这个疑问,我开始了个人OWin学习之旅。

到底什么是OWIN

首先咱们来简单介绍一下OWin,它是由微软ASP.NET小组成员组织成立的一个开源项目。目标是解耦服务器和应用,这里面的服务器主要是指web 服务器,好比说IIS等,全称是Open Web Interface for .Net。OWin能够说是一套定义,默认它是没有什么具体的实现的,那么在它的定义里面是如何实现服务器与应用程序的解耦的呢? 咱们又该如何理解服务器与应用程序的解耦呢?

下面是我的的理解,抛砖引玉,但愿你们多探讨。

问题引入: 为何要解耦服务器与应用程序 ?

既然是服务器和应用程序的解耦,那么这确定是咱们第一个应该考虑的问题。咱们先来简单复习一下ASP.NET 或者是IIS 集成模式管道模型,也就是说一个http请求在进入IIS以后 (咱们这里指7.0及之后版本的集成模式),一直到返回response这中间所经历的步骤。

你们知道,咱们能够开发本身的Http Module去注册这些事件,而后作相应的处理。好比说FormsAuthenticationModule就是注册了AuthenticateRequest事件,而后在这里面去检查用户的cookie信息来判断用户是否登陆的,这里就是一个典型的应用程序与服务器之间的交互问题。而这些事件最后是被IIS触发的,咱们是经过web.config把咱们自定义的http module注册进了iis。回到咱们的问题,若是咱们的网站不运行在iis了,咱们本身开发的这些Http module还能使用么?

另外的问题就是,你们知道咱们在ASP.NET 里面常常用到HttpContext,HttpApplicationt等对象,而ASP.NET全部的处理基本上都离不开这两个对象,由于咱们的Request以及Response都是封装在HttpContext里面的,而这些信息是从IIS中来,最后也是交给IIS处理,由于微软给IIS写代码的时候直接集成了这一块,可是想一下,若是web服务器不是IIS,那么这些信息又从哪里获取呢?

为何须要解耦,是由于他们彼此之间的依懒过大,从而致使咱们不可以轻易的换掉其中任何一个。 即便如今,在web.config添加本身定义的http module 也不是一件能让人开心的事情,反正我一想到那个很长的类名以及程序集名就够蛋疼的。

显然,不少人已经开始意识到,在现在web飞快发展的年代,这种模式已经不可以知足灵活多变的需求。越是轻量级,组件化的东西,越可以快速适应变化,更况且.NET如今要吸引开源社区的注意,只有把这一块打通了,愈来愈多的强大的开源组件才可以出如今.NET的世界里,好比说写一个开源的ASP.NET web服务器。

OWin如何作到解耦

咱们上面说Owin是一套定义,它经过将服务器与应用程序之间的交互概括为一个方法签名,称之为“应用程序代理(application delegate)”

1

AppFunc = Func<IDictionary<string, object>, Task>;

在一个基于Owin的应用程序中的每个组件均可以经过这样的一个代理来与服务器进行交互。 这们这里的交互实际上是与服务器一块儿来处理http request,好比说ASP.NET管理模型中的那些事件,认证,受权,缓存等等,原先咱们是经过自定义的http module,在里面拿到包含了request和response的HttpContext对象,进行处理。而如今咱们能拿到的就是一个Dictionary。

但是别小看了这个Dictionary,咱们全部的信息好比Application state, request state,server state等等这些信息所有存在这个数据结构中。这个dictionary会在Owin处理request的管道中进行传递,没错有了OWin以后,咱们就再也不是与ASP.NET 管道打交道了,而是OWin的管道,可是这个管道相对于ASP.NET 管道而言更灵活,更开放。

这个字典在OWin管道的各个组件中传输时,你能够任意的往里面添加或更改数据。 OWin默认为咱们定义了如下的数据:

有了这些数据之后,咱们就不须要和.NET的那些对象打交道了,好比说ASP.NET MVC中的HttpContextBase, 以及WEB API  中的HttpRequestMessage和HttpResponseMessage。咱们也不须要再考虑system.web 这个dll里的东西,咱们只须要经过OWin就能够拿到咱们想要的信息,作咱们想作的事了。而OWin,它自己和web服务器或者IIS没有任何关系。

微软对OWin的开源实现Katana

咱们上面讲到了OWin只是一套定义,它自己没有任何代码,咱们能够把它当作是微软对外公开的一套标准。那么咱们用到的Microsoft.OWin,这些dll又是从哪里来的呢? 好消息是它是开源的,代码咱们能够从CodePlex上下载,坏消息是它如今尚未比较全的文档,多是我暂时尚未找到。

它包括下面4个组件:

  • Host: 托管咱们应用程序的进程,或者宿主,能够是IIS,能够咱们本身写的程序等。主要是用来启动,加载OWin组件,以及合理的关闭他们
  • Server: 这个Server就是用来暴露TCP端口,维护咱们上面讲到的那个字典数据,而后经过OWin管理处理http请求
  • Middleware : 这个中间件就是用来在OWin管道中处理请求的组件,你能够把它想象成一个自定义的httpModule,它会被注册到OWin管道中一块儿处理http request
  • Application: 这个最好理解,就咱们本身开发的那个应用程序或者说是网站

也就是说咱们用到的Microsoft.OWin,那一系列的dll实现上是叫Katana(武士刀)象征着快,狠,准!下面来一些名词解释,是一些简单的概念有助于你们理解咱们下面要讲的内容(ASP.NET Identity是如何借助 OWin来实现登陆的)。

OWin Application( OWin 应用程序 )

这个程序引入了OWin的dll,同时会使用OWin中的一些组件完成对request的一些处理,好比说咱们下面要讲的OWin 认证。

OWin 组件

咱们也可能管它叫中间件,它经过暴露一个应用程序代理,也就是接收一个IDictionary<string,object>,返回一个Task来参与到OWin对request和处理管道中。

Start up 类

每个OWin的应用程序都须要有一个start up的类,用来声明咱们要使用的OWin组件(即中间件)。Start up 类有如下几种声明方式:

  1. 命名约定: Owin会扫描在程序集的根下名叫 startup的类做为默认启动配置类
  2. OwinStartup 标签

    1

    [assembly: OwinStartup(typeof(StartupDemo.TestStartup))]

  3. config 文件 

    1

    <add key="owin:AutomaticAppStartup " value="false" />

OWin authentication

Owin的很大亮点之一就是它可让咱们的ASP.NET 网站摆脱IIS,可是毕竟大多数的ASP.NET 网站仍是host在IIS上的,因此Katana项目还支持在IIS集成模式中运行Owin组件。 咱们只须要在咱们的项目中加上Microsoft.Owin.Host.SystemWeb这个包就能够了,其实默认MVC5程序已经为咱们加上了。咱们在VS2013中新建一个MVC5的站点,默认会为咱们加上如下的dll:

  • OWin.dll
  • Microsoft.Owin.dll
  • Microsoft.Owin.Host.SystemWeb
  • Microsoft.Owin.Security
  • Microsoft.Owin.Security.Cookie

他们对应nuget中的package:

这就是为何咱们能够拿到Microsoft.Owin.Security.IAuthenticationManager,而后再调用其 SignIn方法和SignOut方法。除了多了这些dll之外,VS还自动帮咱们移除了FormsAuthenticationModule。

Forms 认证

咱们来小小的复杂一下Forms认证,在Forms认证中咱们检测完用户名和密码以后,只须要调用下面的代码就会为咱们建立用户cookie。

1

FormsAuthentication.SetAuthCookie("Jesse", false);

而后FormsAuthenticateionModule会在ASP.NET 管道的 AuthenticateRequest 阶段去检查是否有这个cookie,并把它转换成咱们须要的identity对象,这样的话咱们就不须要每一次都让用户去输入用户名和密码了。因此登陆的过程实现上是这样的。

  1. 用户在没有登陆的状况下访问了咱们须要登陆的页面
  2. FormsAuthenticationModule检查不到用户身份的cookie,没有生成identity对象,HttpContext.User.IsAuthenticated = false
  3. 在ASP.NET 管道 的Authroize 受权阶段,将用户跳转到登陆页面
  4. 用户输入用户名和密码点击提交
  5. 咱们检查用户名和密码,若是正确,就调用FormsAuthentication.SetAuthCookie方法生成登陆cookie
  6. 用户能够正常访问咱们须要登陆的页面了
  7. 用户再次访问咱们须要登陆的页面
  8. FormsAuthenticationModule检查到了用户身份的cookie,并生成identity对象,HttpContext.User.IsAuthenticated = true
  9. ASP.NET 管道的 Authroize受权阶段,HttpContext.User.IsAuthenticated=true,能够正常浏览
  10. 7,8,9 循环

Forms认证有如下几不足:

  1. 用户名直接暴露在cookie中,须要额外的手段去将cookie加密
  2. 不支持claims-based 认证
  3. ....

咱们上面Forms的登陆过程,对于OWin登陆来讲一样适用。咱们在上面讲ASP.NET Identity登陆第二句话的时候已经拿到了ClaimsIdentity,那么咱们接下来要看的问题就是如何借助于IAuthenticationManager 去登陆? FormsAuthenticationModuel没有了,谁来负责检测cookie?您请接着往下看!

MVC 5默认的start up配置类

VS除了为咱们引用OWin相关dll,以及移除FormsAuthenticationModule之外,还为咱们在App_Start文件夹里添加了一个Startup.Auth.cs的文件。

1

2

3

4

5

6

7

8

9

10

11

12

13

public partial class Startup

{

public void ConfigureAuth(IAppBuilder app)

{

// 配置Middleware 組件

app.UseCookieAuthentication(new CookieAuthenticationOptions

{

AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,

LoginPath = new PathString("/Account/Login"),

CookieSecure = CookieSecureOption.Never,

});

}

}

UseCookieAuthentication是一IAppBuilder 的一个扩展方法,定义在Microsoft.Owin.Security.Cookies.dll中。

CookieAuthenticationExtensions.cs

1

2

3

4

5

6

7

8

9

10

11

public static IAppBuilder UseCookieAuthentication(this IAppBuilder app,

CookieAuthenticationOptions options)

{

if (app == null)

{

throw new ArgumentNullException("app");

}

app.Use(typeof(CookieAuthenticationMiddleware), app, options);

app.UseStageMarker(PipelineStage.Authenticate);

return app;

}

将Owin Middleware绑定到IIS 集成模式的管道

UseCookieAuthentication主要调用了两个方法:

  • IAppBuilder.Use : 添加OWin middleware 组件到 OWin 管道
  • IAppBuilder.UseStageMarker : 为前面添加的middleware指定在IIS 管道的哪一个阶段执行。

PepelineStage这个枚举定义和咱们IIS管道的那些顺序,也就是和咱们Http Module里面能够绑定的那些事件是同样的。

咱们能够回顾同样如何在http module中为Authenticate绑定事件。

1

2

3

4

5

6

7

8

9

10

11

public class MyModule : IHttpModule

{

public void Init(HttpApplication context)

{

// 將ctx_AuthRequest 綁定的 AuthenticateRequest 事件

context.AuthenticateRequest += ctx_AuthRequest;

}

void ctx_AuthRequest(object sender, EventArgs e)

{

}

}

Owin这里的Use,貌似是借用了Node.js的用法呀- -! 无论怎么说,经过这样一种方式,咱们就能够将Owin 中间件注册进IIS 集成模式的管道了。也就是说咱们上面注册的CookieAuthenticationMiddleware会在AuthenticaRequest 阶段执行。而它就是真正生成cookie以及读取cookie的那只背后的手。

CookieAuthenticationMiddelware 负责读取用户信息cookie

若是你看的还算认真的话,咱们上面讲claims-based认证的时候有一个小例子。咱们只须要在AuthenticateRequest阶段将ClaimsPrincipal赋给当前的User对象就能够实现登陆了,而这也是IAuthenticationManager.SignIn所作的。可是咱们上面讲Forms登陆的过程同样,用户登陆以后,咱们须要生成cookie,这样用户下次访问的时候就不须要登陆了,咱们在Authenticate Request去检测有没有这个cookie就能够了,CookieAuthenticationMiddleware就负责作了这两件事情。

咱们能够到Katana的站点去下载源码,而后找到CookieAuthenticationMiddleware这个类,而后找到最后生成cookie和读取cookie的类:CookieAuthenticationHandler。

在CookieAuthenticationMiddleware中有两个方法:

  • AuthenticateCoreAsync : 从request中读取cookie值,附给到identity对象,没有什么内幕,就是读读cookie进行解密,转成identity对象。
  • ApplyResponseGrantAsync : 往response中写入cookie值,一样没有什么内幕,有兴趣的同窗能够下载katana源码瞅瞅。

CookieAuthenticationMiddelware 对cookie的加密方式

在咱们上篇文章中对ASP.NET Identity登陆的例子中,若是你登陆了,那么你会发现咱们的cookie是通过加密的。而cookie的名称是以.AspNet.为前缀加上咱们

Startup中配置的AuthenticationType。

那么接下来,咱们就来看一下CookieAuthenticationMiddleware是以什么样的加密方式将咱们的identity信息加密的,咱们能不能将它解回来呢?

欲知后事如何,请听下回分解~ 

参考&小结

这一篇文章涉及到的新东西比较多,也花了我很多的时间。可是总的来讲收获仍是蛮大的,把Claims-based总结性的归纳了一下,而后又开始了Owin的学习之旅。愈来愈发现.NET的强大,在开源社区的不断贡献下.NET也逐渐开始绽开出新的生命力。后面还会继续Owin的学习,有兴趣的朋友能够继续关注!仍是我一直强调的,虽然ASP.NET Identity登陆只有三行代码,可是背后却隐藏的如此之深,若是你不怀着一颗好奇以及好学的心,你永远不知道背后有多么美丽的故事。若是只是习惯了使用框架,而不去了解它,那就会迷失在学习新技术的路上。可是若是你知道它的设计原理,你就会发现其实都差很少的,思想就是那么一套,可是外观却可能随时改变。 另外的话我以为这篇文章写的不错,值得点赞,你以为呢?

我是Jesse Liu,关注我,跟我一块儿探寻.NET 那些新鲜,好玩,以及背后的故事吧 :)

参考资料:

  • http://owin.org/
  • http://brockallen.com/2013/10/24/a-primer-on-owin-cookie-authentication-middleware-for-the-asp-net-developer/
  • http://www.asp.net/aspnet/overview/owin-and-katana/an-overview-of-project-katana
  • http://www.asp.net/aspnet/overview/owin-and-katana/owin-middleware-in-the-iis-integrated-pipeline
  • http://www.asp.net/aspnet/overview/owin-and-katana/owin-startup-class-detection
  • http://msdn.microsoft.com/en-us/library/ff359101.aspx

做者:Jesse 出处: http://jesse2013.cnblogs.com/

本文版权归做者和博客园共有,欢迎转载,但未经做者赞成必须保留此段声明,且在文章页面明显位置给出原文链接,不然保留追究法律责任的权利。若是以为还有帮助的话,能够点一下右下角的【推荐】,但愿可以持续的为你们带来好的技术文章!想跟我一块儿进步么?那就【关注】我吧。

【关注】Jesse Liu

相关文章
相关标签/搜索