隨着軟件的不斷髮展,出現了更多的身份驗證使用場景,除了典型的服務器與客戶端之間的身份驗證外還有,如服務與服務之間的(如微服務架構)、服務器與多種客戶端的(如PC、移動、Web等),甚至還有須要以服務的形式開放給第三方的,身份驗證這一功能已經演化爲一個服務,不少大型應用中都有本身的身份驗證服務器甚至集羣,因此普通的身份驗證方式已經不能知足需求。html
在.Net領域中也有一些開源的身份驗證服務器組件,如IdentityServer(http://identityserver.io/),可是這些組件對於一些規模較小的項目來講可能會感受到比較龐大,增長了學習和維護成本,因此本章將對OAuth以及如何使用OAuth實現身份驗證模式進行介紹。
本章的主要內容有:angularjs
● OAuth2.0簡介
● 在.Net中使用OAuth實現基於受權碼模式的身份驗證
● 實現基於Access Token的身份驗證
● 加入Refresh Token支持
● 實現經過用戶密碼模式獲取Access Token
● 實現客戶端模式獲取Access Token
● 關於.Net中OAuth相關令牌的加密說明web
注:本章內容源碼下載:https://files.cnblogs.com/files/selimsong/OAuth2Demo.zip數據庫
在文章的開始的時候說過現代軟件應用的身份驗證場景愈來愈豐富,下圖是現代應用程序的一個通訊圖,它描述了常見的「客戶端」是如何與服務器提供的服務通訊的。api
該圖出自IdentityServer:https://identityserver4.readthedocs.io/en/release/intro/big_picture.html
爲了知足這些場景人們制定了一套標準協議,這個協議就是OAuth(Open Authorization,開放受權)協議,OAuth可以讓第三方應用程序去訪問受限制的HTTP服務。OAuth有兩個版本分別是1.0和2.0,可是因爲1.0版本過於複雜因此1.0版本被2.0版本替換了,而且兩個版本是不兼容的。
接下來就對OAuth2.0相關的概念進行介紹:瀏覽器
● Resource Owner:資源擁有者,就是可以訪問被限制資源的用戶(注:這裏的用戶是個泛指,它既能夠是真實用戶也能夠是服務程序)。
● Resource Server:資源宿主,可以接受和處理,使用訪問令牌(access token)訪問受保護資源的請求(如提供API的服務器)。
● Client:它泛指全部的第三方程序(不管是Web應用、桌面應用仍是服務端應用),它經過資源擁有者以及它的受權來訪問受保護的資源。
● Authorization Server:用來對受權成功的客戶端發佈令牌以及對令牌的驗證受權。並對Client進行管理。安全
A. 第三方程序向資源擁有者(用戶)發送受權請求,這個過程既能夠經過客戶端直接向用戶請求,也能夠經過受權服務器做爲中介來完成請求。(注:對於受權請求這個概念至關於用戶登陸,應用程序能夠直接顯示一個登陸頁面,也能夠跳轉到驗證服務器的統一登陸頁面)
B. 用戶將受權相關信息「提交」給第三方程序,在OAuth中有4種不一樣的權限授予方式,每種方式須要的數據不一樣,如基於用戶密碼的受權方式就須要用戶名和密碼。
C. 第三方程序將用戶的受權信息提交到受權服務器,請求一個Access Token。
D. 受權服務器驗證完成用戶的受權信息後,將Access Token發放到第三方程序。
E. 第三方程序攜帶Access Token訪問被保護的資源。
F. 資源服務器驗證Access Token有效後,將資源返回到第三方程序。服務器
● Authorization Code(受權碼模式):該模式的核心是客戶端經過一個受權碼來向受權服務器申請Access Token。是一種基於重定向的受權模式,受權服務器做爲用戶和第三方應用(Client)的中介,當用戶訪問第三方應用是,第三方應用跳轉到受權服務器引導用戶完成身份驗證,生成Authorization Code並轉交到第三方應用,以便於第三方應用根據這個受權碼完成後續的Access Token獲取。
● Implicit(簡化模式):簡化模式是一種簡化的受權碼模式,受權碼模式在首次訪問第三方應用時跳轉到受權服務器進行身份驗證返回受權碼,而簡化模式在跳轉到受權服務器後直接返回Access Token,這種模式減小了獲取Access Token的請求次數。
● Resource Owner Password Credentials(用戶密碼模式):經過資源擁有者(用戶)的用戶名和密碼來直接獲取Access Token的一種方法,這種方法要求第三方應用(Client)是高度可信任的,而且其它受權方式不可用的狀況下使用。
● Client Credentials(客戶端模式):該模式是經過第三方應用(Client)發送一個本身的憑證到受權服務器得到Access Token,這種模式的使用要求該Client已經被受權服務器管理並限制其對被保護資源的訪問範圍。另外這種模式下Client應該就是一個資源擁有者(用戶),如微服務程序。架構
這個很好理解,第三方應用經過Access Token去獲取受保護的資源,可是Access Token是存在有效期的,一旦過時就沒法使用,爲了不Access Token過時後沒法使用,因此加入了Refresh Token的概念,經過刷新的方式來完成Access Token的更新。app
在OAuth2.0中,全部須要訪問受限資源的程序都視爲第三方應用(Client),爲了保證這個Client是安全的、可信任的,因此OAuth須要對Client進行管理。參考:https://tools.ietf.org/html/rfc6749#section-2
這裏終結點表明的是HTTP資源,在OAuth受權過程當中須要使用到一些終結點的支持,如Authorization code(受權碼)的獲取,以及Access Token的獲取,終結點由受權服務器提供。參考:https://tools.ietf.org/html/rfc6749#section-3
Access Token的類型是讓Client根據具體類型來使用Access Token完成對受保護資源的請求。
OAuth2.0中有兩種類型分別是Bearer和Mac,它們體現方式以下:
● Bearer:
● Mac:
參考:https://tools.ietf.org/html/rfc6750
OAuth2.0是一個開放標準,既然是標準那麼就能夠有實現,在.Net中微軟基於Owin實現了OAuth2.0協議,下面就介紹如何在ASP.NET MVC程序中實現OAuth身份驗證。
注:本例基於ASP.NET MVC默認帶身份驗證模板完成。
經過NuGet安裝Microsoft.Owin.Security.OAuth組件:
注:從該組件的名稱能夠看出,.Net對OAuth的實現其實是基於Owin的,因此不少內容均使用Owin中相關的身份驗證概念,這些內容可參考本系列與身份驗證的文章。
根據上面OAuth的介紹可知,受權服務器是OAuth其中一個角色,該角色最主要的功能就是Access Token的發放以及受權,另外它還用於支持受權碼模式的受權碼發放以及Client的管理。
在Startup類型的Configuration方法中加入如下代碼,該代碼是爲Owin中間件添加一個受權服務器(注:該中間件是一個Owin的身份驗證中間件可參考《ASP.NET沒有魔法——ASP.NET Identity 的「多重」身份驗證》)。
其中OAuthAuthorizationServerOptions定義以下:
上面的定義能夠分爲如下幾類:
● 終結點地址:AuthorizeEndpointPath、TokenEndpointPath等,它定義了訪問獲取受權碼以及獲取Token的地址信息。
● Token提供器:AuthorizationCodeProvider、AccessTokenProvider、RefreshTokenProvider負責完成對應令牌的建立和處理功能。
● Token的「加密」與「解密」:該功能是OAuth與Owin身份驗證的結合,經過AccessTokenFormat等ISecureDataFormat接口的實現能夠將對應的Token轉換成一個 AuthenticationTicket。可參考《ASP.NET沒有魔法——ASP.NET Identity的加密與解密》文中TicketDataFormat的用法。
● OAuth受權服務:Provider是整個OAuth服務器的核心,它包含了終結點的處理與響應、OAuth中的4種Access Token受權方式和刷新令牌獲取Access Token的方式以及請求、客戶端的相關驗證:
上面介紹OAuth時介紹了終結點實際上就是用來獲取受權碼或者Access Token的,在.Net中使用Microsoft.Owin.Security.OAuth組件僅須要經過配置的形式就能夠指定受權碼及Token獲取的終結點訪問地址(注:把AllowInsecureHttp配置屬性設爲true,能夠容許不安全的http來訪問終結點,該配置僅用於開發環境):
完成後就能夠經過瀏覽器訪問這兩個地址:
能夠看到是能夠訪問,只不過是有錯誤的(注:請求地址的QueryString的參數參考文檔)。
Client在OAuth中指代了全部的第三方須要訪問受限制資源的應用程序,受權服務器爲了可以識別和驗證Client因此須要完成Client的管理以及驗證功能。(注:微軟在Microsoft.Owin.Security.OAuth組件中僅僅提供了Client驗證的接口,因此要本身實現Client數據的管理以及驗證邏輯):
1). 添加Client實體以及對應的倉儲(本例之內存的方式實現倉儲,實際使用中至少應該保存數據庫):
上圖是Client最基礎的屬性(注:若是還須要對Client的訪問範圍進行限制,那麼還應該加入一個Scope的列表,本例再也不加入Scope的限制)。
2). Client的倉儲:
3). 實現受權服務器對Client的驗證:
因爲受權服務器對客戶端驗證的接口位於OAuthAuthorizationServerProvider類型中,因此首先要繼承該類型,並重載相應的驗證方法:
上面代碼作了如下幾件事:
● 嘗試從Http請求header或者請求body中獲取Client信息,包含Id和密碼。
● 若是沒有Client的Id信息,那麼直接判斷爲不經過驗證,若是有Client的密碼信息則保存到Owin上下文中,供後續處理使用。
● 使用得到的ClientId在Client倉儲中查詢,判斷是不是一個合法的Client,如不是則判斷爲不經過驗證。
4). 驗證完成後設置該Client的重定向Url(注:該方法仍舊是重載OAuthAuthorizationServerProvider類型中的方法):
受權碼的生成是受權服務器終結點的一項功能,當使用受權碼模式時,用戶訪問Client會被引導跳轉到受權服務器完成身份驗證(登陸),隨後又攜帶受權碼跳轉回Client,Client使用該受權碼獲取Access Token。在OAuth的.Net實現中,須要經過在配置中配置一個類型爲IAuthenticationTokenProvider的令牌提供器,該提供器用於建立和解析令牌,這裏的建立實際就是用戶完成登陸後受權碼的生成以及受權碼和用戶登陸身份信息的關聯,而解析實際就是根據受權碼得到對應用戶身份信息並生成Access Token的過程。
下面就經過實現IAuthenticationTokenProvider的方式實現一個自定義受權碼提供器:
從上面代碼能夠看出這個提供器的核心功能是以Guid的方式生成一個鍵值(受權碼)保存了當前用戶的信息,當解析時經過該鍵值(即受權碼)獲取用戶身份信息。(注:AuthenticationTokenCreateContext對象用於對當前用戶身份信息AuthenticationTicket對的的序列化和反序列化)
完成後將該提供器配置到受權服務器中間件中:
當用戶訪問受權碼終結點時理應讓用戶知道Client須要他的受權,爲此在ASP.NET MVC程序中須要添加一個路由與受權碼終結點地址匹配的Controller、Action以及View:
1). Controller及Action(注:該Action須要經過身份驗證,若是沒有須要跳轉到登陸頁面完成身份驗證後纔可訪問):
2). View:顯示受權提示
1). 訪問受權碼終結點獲取受權碼:http://localhost:59273/oauth2/authorize?response_type=code&client_id=test1
因爲沒有登陸,因此先跳轉到登陸頁面。
完成登陸後跳轉回受權頁面:
點擊受權按鈕後,攜帶受權碼跳轉到test1這個client的重定向Url(注:此處test1這個Client設置的Url就是受權服務器自己,因此看上去沒有作重定向)
獲得受權碼後,攜帶受權碼訪問Access Token終結點獲取Access Token(注:這裏使用Chrome瀏覽器的Postman拓展來實現請求的模擬):
注:上面響應信息中的access_token包含了加密後用戶的身份信息,其加密過程可參考基於Cookie的用戶信息加密過程。ASP.NET沒有魔法——ASP.NET Identity的加密與解密
上面介紹瞭如何基於受權碼模式得到Access Token,接下來將介紹如何使用Access Token來訪問受限制的資源(注:本例中的資源服務器與受權服務器位於同一實例中,因此當資源服務器對access token解密時,可以保證與受權服務器用於生成access token所用密鑰一致,可以正常解密,這裏的Access Token和基於Cookie的身份驗證中的身份驗證Cookie性質是相同的,都是將用戶的身份信息序列化後的加密字符串)
1. 在Startup類中添加基於Bearer的OAuth身份驗證中間件:
2. 添加訪問受限制的資源:
3. 訪問受限資源:
未添加受權信息直接跳轉到登陸頁面。
添加Access Token後可正常訪問資源:
上面使用受權碼模式生成的Access Token是存在過時時間的(實際上不管什麼方式生成的Access Token都存在過時時間),Token過時後又不可能讓用戶再受權一次,因此須要使用Refresh Token來按期刷新Access Token,.Net中實現Refresh Token的方式與受權碼相似,在生成Refresh Token的同時會關聯用戶的身份信息,後續可使用這個Refresh Token來生成新的Access Token。
1. 建立Refresh Token提供器(實現方式與受權碼提供器基本一致):
2. 爲受權服務器配置Refresh Token提供器:
3. 再次獲取到受權碼後,根據該受權碼獲取Access Token,返回信息中將攜帶Refresh Token:
4. 根據Refresh Token刷新Access Token:
上面介紹了受權碼模式的實現方式,但這種方式的核心其實是創建了一個受權碼和用戶信息的映射(包括刷新令牌方式也是創建了刷新令牌與用戶信息的映射),後續的Access Token其實是使用這個了用戶信息生成的。換句話用戶信息纔是核心,.Net中用戶信息的體現從底到高分別是:IIdentity->ClaimsIdentity-> AuthenticationTicket,關於用戶的身份信息可參考:《ASP.NET沒有魔法——ASP.NET Identity與受權》,在基於受權碼的模式時經過在受權服務器的登陸功能得到了用戶信息,而基於用戶名密碼模式時沒有這個跳轉登陸環節,因此須要直接經過用戶名密碼來獲取用戶信息,其實現以下重載了OAuthAuthorizationServerProvider類型的GrantResourceOwnerCredentials方法:
該方法從Owin環境中獲取Identity中的UserManager對象,經過UserManager來驗證用戶是否存在,若是存在則將使用用戶信息來建立一個ClaimsIdentity對象(注:此處是省略的實現,正常實現可根據需求參考Cookie驗證方式將Scope或者Role等信息也添加到Identity對象中)。另UserManager是經過如下代碼添加到Owin上下文中的,它的Key值是"AspNet.Identity.Owin:" + typeof(ApplicationUserManager).AssemblyQualifiedName。
使用用戶名密碼獲取Access Token:
客戶端模式和用戶名密碼模式是相似的,它是經過Client的Id以及密碼來進行受權,使用的是Client相關的信息,它的實現方式以下,重載GrantClientCredentials方法,經過客戶端驗證後的id和密碼信息來驗證改Client是否合法,對於合法的Client爲其建立Identity對象(注:此處能夠根據實際需求在Identity中添加相應的屬性):
使用Client信息獲取Access Token:
以上就是.Net中對於OAuth的實現,另外.Net中沒有提供簡化模式的接口,可是提供了一個GrantCustomExtension,也就是說受權模式是可拓展的。
本例中除了受權碼以及刷新令牌是2個Guid鏈接外,訪問令牌(包括全部受權模式生成的令牌)以及受權碼對應的用戶信息、刷新令牌對應的用戶信息都是通過加密的,其加解密對象建立過程以下,具體內容可參考《ASP.NET沒有魔法——ASP.NET Identity的加密與解密》
本章內容介紹了OAuth2.0協議相關的內容,並經過一個ASP.NET MVC程序基於微軟的Microsoft.Owin.Security.OAuth組件實現了該協議中的大部分功能。使用OAuth來實現身份驗證可讓咱們的應用程序從Web拓展至任意的平臺上運行,但這樣的實現仍舊是存在一些問題的,在下一篇文章中將對這些問題進一步的討論和介紹。
PS.這一章內容比較多,若有問題能夠在評論區留言,另外最近事情比較多,因此更新慢了,感謝你們的支持。
參考:
https://stackoverflow.com/questions/39909419/jwt-vs-oauth-authentication
http://www.cnblogs.com/linianhui/p/oauth2-authorization.html
http://www.c-sharpcorner.com/UploadFile/4b0136/openid-connect-availability-in-owin-security-components/
https://docs.microsoft.com/en-us/aspnet/aspnet/overview/owin-and-katana/owin-oauth-20-authorization-server
https://security.stackexchange.com/questions/94995/oauth-2-vs-openid-connect-to-secure-api
http://bitoftech.net/2014/07/16/enable-oauth-refresh-tokens-angularjs-app-using-asp-net-web-api-2-owin/