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

相關文章
相關標籤/搜索