ASP.NET沒有魔法——ASP.NET OAuth、jwt、OpenID Connect

来源:https://www.cnblogs.com/selimsong/archive/2018/01/23/8184904.html
-Advertisement-
Play Games

上一篇文章介紹了OAuth2.0以及如何使用.Net來實現基於OAuth的身份驗證,本文是對上一篇文章的補充,主要是介紹OAuth與Jwt以及OpenID Connect之間的關係與區別。 本文主要內容有: ● Jwt簡介 ● .Net的Jwt實現 ● OAuth與Jwt ● .Net中使用Jwt ...


  上一篇文章介紹了OAuth2.0以及如何使用.Net來實現基於OAuth的身份驗證,本文是對上一篇文章的補充,主要是介紹OAuth與Jwt以及OpenID Connect之間的關係與區別。

  本文主要內容有:
  ● Jwt簡介
  ● .Net的Jwt實現
  ● OAuth與Jwt
  ● .Net中使用Jwt Bearer Token實現OAuth身份驗證
  ● OAuth與OpenID Connect

  註:本章內容源碼下載:https://files.cnblogs.com/files/selimsong/OAuth2Demo_jwt.zip

Jwt簡介

  Jwt(Json Web Token)它是一種基於Json用於安全的信息傳輸標準,Jwt具有以下幾個特點:
  ● 緊湊:Jwt由於是為Web準備的,所以就需要讓數據儘可能小,能夠在Url、Post參數或者Http Header中攜帶Jwt,同時由於數據小,所以也增加了數據傳輸的速度。
  ● 自包含:在Jwt的playload部分包含了所有應該包含的信息,特別是在Jwt用於身份驗證時playload中包含了用戶必要的身份信息(註:不應該包含敏感信息),這樣在進行身份驗證時就無需去資料庫中查詢用戶信息。
  ● 可信:Jwt是帶有數字簽名的,可以知道Jwt在傳輸過程中是否被篡改,保證數據是完整的,可用的簽名演算法有RS256(RSA+SHA-256)、HS256(HMAC+SHA-256)等。

  Jwt有兩個用途,其一是用於數據交互,因為Jwt是被簽名的,可以保證數據的完整性。另外就是用來攜帶用戶信息進行身份驗證

  Jwt包含三個部分:
  ● Header:包含了簽名演算法以及令牌類型(預設為JWT)。如:

  

  註:alg以及typ均是縮寫,其目的就是為了減小jwt的大小。

  ● Playload:包含Jwt所攜帶的信息內容,Playload中包含了3種類型的Claim(聲明)定義,分別是標準的,如iss(issuer,Jwt的發行者)、sub(subject,Jwt所代表的用戶)、aud(audience,Jwt的接收者)、exp(expiration time,Jwt的過期時間),還有一些是公共約定的如: http://www.iana.org/assignments/jwt/jwt.xhtml,另外就是私有自定義的,這些用來存放具體的信息。
  Playload的結構如下:

  

  ● Signature:包含了Header以及Playload的base64Url編碼後的簽名結果,其計算過程如下:

  

  最終三個部分均使用Base64Url的方式進行編碼後使用符號“.”進行分隔,以下是一個完整Jwt的例子:

  

  註:Jwt中的數據是透明的,既任何人拿到數據都能Base64Url反編碼的形式看到內容,簽名僅僅是保證內容不被纂改,所以不能在Jwt中包含敏感數據。以上例子均來自https://jwt.io/introduction/ 

.Net的Jwt實現

  Jwt是一個標準,在https://jwt.io/上可以看到很多不同語言對Jwt的實現,而.Net的其中一個實現是System.IdentityModel.Tokens.Jwt組件,該組件是由微軟實現的,它有兩個重要的類型分別是:
  註:從名稱(IdentityModel)都可以看出,微軟的這個實現主要是用於身份驗證的,如果使用Jwt的目的不是身份驗證可以選擇其它的組件或自定義實現。
  ● JwtSecurityToken:這個類型是Jwt的一個封裝,它除了包含Jwt的三個要素(Header、Playload、Signature)外,還拓展了一些如Subject、Iusser、Audiences、有效期、簽名演算法、簽名密鑰等重要屬性。
  下圖是JwtSecurityToken的部分定義:

  

  ● JwtSecurityTokenHandler:該對象用來對Jwt進行操作,如Jwt的創建、驗證( 包含發佈者、接收者、簽名等驗證)、Jwt的序列化與反序列化(字元串形式與對象形式之間的轉換)
  下圖是JwtSecurityTokenHandler的部分定義:

  

OAuth與Jwt

  OAuth與Jwt前者是一個授權協議後者是一個信息安全傳輸標準,看起來它們之間並沒有什麼關係,但其實OAuth的Access Token有一種實現方式就是Jwt。
為什麼要使用Jwt來作為OAuth的Access Token?首先來看一下上一篇文章中生成的Access Token:

  

  它是一個加密後的字元串,該字元串包含了用戶的相關信息,但是該字元串只能夠被使用Microsoft.Owin.Security.OAuth組件的應用程式解密(不包括參照源碼的實現),並且還要保證加解密的密鑰是相同的。但是OAuth很多時候是用於一些分散式的場景中,甚至還會使用不同語言來編寫不同的應用、服務。這樣的話上面這種Token的實現方式就無法滿足需求。
  所以需要使用Jwt Bearer Token來解決不同應用中的Token識別問題

.Net中使用Jwt Bearer Token實現OAuth身份驗證

  在上一篇文章中提到了Microsoft.Owin.Security.OAuth組件中Access Token的生成實際上是對一個AuthenticationTicket對象序列化並加密後的字元串,而Access Token的驗證則是對加密後的字元串解密並反序列化獲得AuthenticationTicket對象的過程。
  而對於Access Token來說無論是Microsoft.Owin.Security.OAuth組件的實現方式還是Jwt,甚至是自定義格式,它的核心都在於如何將用戶信息包含到一個字元串令牌中,並且能夠通過這個字元串令牌還原出正確的用戶信息。對於這一個過程在.Net的Owin身份驗證解決方案中將其抽象為一個ISecureDataFormat<TData>介面,其中身份驗證的泛型TData類型為AuthenticationTicket。下圖是ISecureDataFormat介面的定義,它的兩個方法就是用於進行字元串加密令牌與用戶信息對象之間的轉換,可參考《ASP.NET沒有魔法——ASP.NET Identity的加密與解密

  

  上一篇文章中也給出了Microsoft.Owin.Security.OAuth組件中,預設對Access Token加解密對象是TicketDataFormat,該對象實際上就是一個實現了ISecureDataFormat介面的類型,用於通過數據保護器來完成數據對象的序列化與加解密的工作,可參考《ASP.NET沒有魔法——ASP.NET Identity的加密與解密》:

  

  可以這樣理解要在.Net中實現基於Jwt Bearer Token的OAuth身份驗證,僅需要在Microsoft.Owin.Security.OAuth組件的基礎上自定義一個ISecureDataFormat<AuthenticationTicket>類型即可

Jwt主要屬性的說明

  實現之前再次對Jwt的一些重要屬性進行說明:
  ● Issuer:發佈者,Jwt裡面包含並且會進行驗證的信息,Token的發佈者,該發佈者實際上就是身份驗證伺服器本身。
  ● Audience:觀眾,發佈者生成一個Token是根據觀眾來生成的,因為整個驗證體系是以發佈者為中心的分散式的包含多種應用的,為了保證數據安全一個Token只應該針對其中一個應用有效,所以在驗證Jwt時還要對Audience進行驗證。
  ● Subject:主題,在身份驗證中一般用於保存用戶信息,如用戶名。

  它們三的關係如下圖:

  

  User代表的就是Subject,在OAuth中有Client的概念,OAuth的Client就相當於Audience。之前已經實現了Client的管理,現在為每一個Client添加一個用來數字簽名的密鑰,該密鑰是一個32位byte數組的Base64編碼字元串。另外這裡是使用HMAC演算法來完成對Token的摘要計算。

  

實現一個基於Jwt的ISecureDataFormat<AuthenticationTicket>

  下麵就開始介紹如何來實現這個ISecureDataFormat:
  1. 通過Nuget安裝Microsoft.Owin.Security.Jwt組件:
  註:微軟實現了一個用於解析Jwt Bearer Token的組件,但是該組件只實現了Unprotect方法,使用這個組件開發可以減少一些工作量。

  

  2. 瞭解Microsoft.Owin.Security.Jwt中JwtFormat類型:
  Microsoft.Owin.Security.Jwt中實現了一個JwtFormat的對象,該對象正好實現了需要的ISecureDataFormat介面:

  

  但是從源碼中得知該對象沒有實現Protect方法:

  

  而它的UnProtect方法的實現主要工作如下:

  

  ● 對發佈者以及Token的簽名、過期時間等進行驗證(註:驗證操作是由System.IdentityModel.Tokens.Jwt組件中的JwtSecurityTokenHandler類型提供的)。
  ● 驗證成功後獲取Token中包含的用戶信息。

  3. 實現Jwt的Protect方法:

  

  完整代碼:

 1     public class MyJwtFormat :  ISecureDataFormat<AuthenticationTicket>
 2     {
 3         //用於從AuthenticationTicket中獲取Audience信息
 4         private const string AudiencePropertyKey = "aud";
 5 
 6         private readonly string _issuer = string.Empty;
 7         //Jwt的發佈者和用於數字簽名的密鑰
 8         public MyJwtFormat(string issuer)
 9         {
10             _issuer = issuer;
11         }
12 
13         public string Protect(AuthenticationTicket data)
14         {
15             if (data == null)
16             {
17                 throw new ArgumentNullException("data");
18             }
19             //獲取Audience名稱及其信息
20             string audienceId = data.Properties.Dictionary.ContainsKey(AudiencePropertyKey) ?
21                 data.Properties.Dictionary[AudiencePropertyKey] : null;
22             if (string.IsNullOrWhiteSpace(audienceId)) throw new InvalidOperationException("AuthenticationTicket.Properties does not include audience");
23             var audience = ClientRepository.Clients.Where(c => c.Id == audienceId).FirstOrDefault();
24             if (audience == null) throw new InvalidOperationException("Audience invalid.");
25             //根據密鑰創建用於數字簽名的SigningCredentials,該對象在JwtSecurityToken中使用
26             var keyByteArray = TextEncodings.Base64Url.Decode(audience.Secret);
27             var signingKey = new InMemorySymmetricSecurityKey(keyByteArray);
28             var signingCredentials = new SigningCredentials(signingKey,
29                 SecurityAlgorithms.HmacSha256Signature, SecurityAlgorithms.Sha256Digest);
30             //獲取發佈時間和過期時間
31             var issued = data.Properties.IssuedUtc;
32             var expires = data.Properties.ExpiresUtc;
33             //創建JwtToken對象
34             var token = new JwtSecurityToken(_issuer,
35                 audienceId, 
36                 data.Identity.Claims,
37                 issued.Value.UtcDateTime,
38                 expires.Value.UtcDateTime,
39                 signingCredentials);
40             //使用JwtSecurityTokenHandler將Token對象序列化成字元串
41             var handler = new JwtSecurityTokenHandler();
42             var jwt = handler.WriteToken(token);
43             return jwt;
44         }
45 
46         public AuthenticationTicket Unprotect(string protectedText)
47         {
48             throw new NotImplementedException();
49         }
50     }
View Code 

  上面代碼做了以下幾件事:
  ● 從AuthenticationTicket中獲取Audience信息(註:AuthenticationTicket是.Net中用來保存用戶信息的對象,它除了用戶信息,如用戶名以及用戶Claims之外還攜帶了身份驗證的有效期等附加信息,見下圖。AuthenticationTicket的創建方式有兩種,其一是登錄時,在判斷登錄信息無誤後,從資料庫中獲取相應的用戶信息以及從配置(或者預設)獲取身份驗證信息,如有效期等。另外就是通過反序列化身份Token獲取。這裡的Protect方法實際上就是序列化Token的方法,所以它得到的AuthenticationTicket是通過第一總方式創建的)

  

  ● 創建用於數字簽名的SignatureCredentials對象,該對象代表了用於數字簽名的演算法及其密鑰,創建該對象的原因僅僅是JwtSecurityToken對象需要它來完成Token創建。
  ● 通過JwtSecurityToken對象創建Token,該對象的創建需要發佈者(issuer)、觀眾(audience)、用戶Claims信息、發佈時間、有效期以及數字簽名需要的演算法及密鑰等。
  ● 通過JwtSecurityTokenHandler完成對Token的序列化。

  3. 在AuthenticationTicket中加入Audience信息。
  上面在創建Token時提到了需要Audience信息,而Token是通過AuthenticationTicket創建的,所以需要在創建AuthenticationTicket時加入Audience信息,另外上面也提到AuthenticationTicket的兩種創建方法,這裡使用的方法就是在“登錄”時創建的,而OAuth的“登錄”是通過不同類型的“授權”方式實現的,所以要加入Audience信息,只需要在相應方式的授權代碼中添加即可(以基於用戶名、密碼的模式為例,其它方法複製代碼即可):

  

  4. 為Audience(Client)添加用於解析Token的JwtBearerAuthentication中間件:

  

  Audience或者說Client包含了受限制的資源,當要訪問這些資源時就需要解析Token完成身份驗證。而Audience之間或者是Client之間是相對獨立的,所以它應該限制可訪問的Audience以及擁有自己的加密密鑰,甚至還需要驗證發佈者以確定token的安全性。(註:本例將身份驗證伺服器和Client都包含在同一個應用中,實際應用可將其分開,這樣就是一個簡單的單點登錄系統)。

  5. 運行程式

  

  使用該Token能夠正常訪問受限資源:

  

  下麵是將Token Base64解碼後的結果,可以看到Jwt包含的信息:

  

  如果使用test2這個Client獲取的Token,將無法訪問test1保護的資源:

  

  身份驗證失敗,跳轉登錄頁面:

  

OAuth與OpenID Connect

  OAuth與OpenID Connect是經常一起出現的兩個名詞,前者在本系列文章中已經進行過介紹,OAuth是一個授權協議,但是有點矛盾的就是身份驗證和授權實際上是兩個概念,前面文章也提到過的,身份驗證的目的是知道“你”是誰,而授權則是判斷“你”是否有許可權訪問資源。但是從上一篇文章開始介紹的OAuth相關的內容都是用來做身份驗證。授權協議用來做身份驗證,所以說是矛盾的。
  OpenID Connect就是為了彌補OAuth協議的缺陷,而在OAuth協議基礎上進行補充拓展的一個身份驗證協議。它包含瞭如發現服務、動態註冊、Session管理、註銷機制等新的高級特性。
  使用OAuth來做身份驗證,只是因為OAuth相對簡單,適合小型項目,這個與OAuth是授權協議還是身份驗證協議無關,它關註的是能否滿足需求,包括app.UseOAuthBearerAuthentication方法名稱都是Authentication而不是Authorization,通過添加OAuth Bearer身份驗證中間件來實現身份驗證。OpenID Connect更適合於大型項目,在這裡就不再深入介紹。

小結

  本章介紹了Jwt以及Jwt在.Net中的實現,並介紹了在.Net中如何使用Jwt Token實現基於OAuth的身份驗證。使用Jwt Token最主要的是為瞭解決不同應用的Token識別問題。
  最後簡單的說明瞭OAuth與OpenID Connect的區別,它們取捨的關鍵點在於需求,對於小型應用來說OAuth就能夠滿足,而由於OpenID Connect非常複雜,如果有需求時也可以先考慮使用如IdentityServer這些開源組件。

  

  與身份驗證相關的內容暫時到此,關於.Net安全相關內容可以參考下麵的博客,非常全麵包含了身份驗證以及.Net中的加解密等內容:https://dotnetcodr.com/security-and-cryptography/ 

參考:

  https://dzone.com/articles/whats-better-oauth-access-tokens-or-json-web-token
  https://stackoverflow.com/questions/32964774/oauth-or-jwt-which-one-to-use-and-why
  http://openid.net/specs/draft-jones-oauth-jwt-bearer-03.html
  https://tools.ietf.org/html/rfc7523
  https://auth0.com/learn/json-web-tokens/
  https://stackoverflow.com/questions/39239051/rs256-vs-hs256-whats-the-difference
  https://stackoverflow.com/questions/18677837/decoding-and-verifying-jwt-token-using-system-identitymodel-tokens-jwt
  http://www.c-sharpcorner.com/UploadFile/4b0136/openid-connect-availability-in-owin-security-components/
  https://security.stackexchange.com/questions/94995/oauth-2-vs-openid-connect-to-secure-api
  https://www.cnblogs.com/linianhui/archive/2017/05/30/openid-connect-core.html

本文鏈接:http://www.cnblogs.com/selimsong/p/8184904.html 

ASP.NET沒有魔法——目錄


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1.裝飾器: 本質是函數,(裝飾其他函數)就是為其他函數添加附加功能 原則: 1)不能修改被裝飾的函數的源代碼 2)不能修改被裝飾的函數的調用方式 2.實現裝飾器知識儲備: 1)函數即“變數” 2)高階函數 a.把一個函數當作實參傳給另一個函數(可以做到不修改被裝飾函數的源代碼的情況下為其添加功能) ...
  • 首先獲取兩鐘不同玉米的產量數據。 因為用了python的pandas包,因此讀取數據前需要先引入pandas包。 圖1 不同玉米用A B 表示,以下是讀取了數據的前5行。 圖2 分別把品種 A和品種B 的 玉米產量賦值給X和Y 。 然後用scipy.stats 包中的 levene 函數 檢驗數據方 ...
  • 操作系統 : CentOS7.3.1611_x64 go 版本 : go1.8.3 linux/amd64 vim版本 :version 7.4.160 vim配置go語言語法高亮的問題已經遇到過好幾次了,每次都去查找太麻煩,這裡總結下。 安裝git: 安裝vim-go : 配置vimrc文件: 如 ...
  • 前言 redis 大家都使用過, 可以安裝在windows下, 也可以安裝在linux下, 一般還是linux下安裝比較多. 這裡來介紹一下redis在linux下的安裝 一. 下載 https://redis.io/ 二. 安裝 1. 下載之後, 將文件拷貝到 linux 中. 我這裡是放在 /h ...
  • ###反射## getattr,hasattr,setattr,delattr,和類裡面的欄位有關,具體看例子 #1 class Person: def __init__(self,name,age): self.name = name self.age = age def show_lover(s ...
  • java.util.Date 就是在除了SQL語句的情況下麵使用 java.sql.Date 是針對SQL語句使用的,它只包含日期而沒有時間部分 直接說就是:java.sql.Date就是與資料庫Date相對應的一個類型,而java.util.Date是純java的Date 它都有getTime方法 ...
  • 1:列表: Python的列表比C語言的數組強大的多,數組只能存放相同類型的數據,而列表則像一個大集裝箱可以存放整形、浮點型、字元串、對象等 2:創建列表的方法 3:向列表中添加元素的方法 1)append() 向列表末尾添加一個參數 2)extend() 參數為一個列表,從原列表擴展原有列表 3) ...
  • 在 《不一樣的Interceptor》中我們著重介紹了Dora.Interception中最為核心的對象Interceptor,以及定義Interceptor類型的一些約定。由於Interceptor總是通過攔截某個方法的調用進而實現對前置或者後置操作的註入,所以我們定義的Interceptor類型... ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...