c#關於JWT跨域身份驗證解決方案

  • 2019 年 10 月 15 日
  • 筆記

學習程式,不是記程式碼,而是學習一種思想,以及對程式碼的理解和思考。

JSON Web Token(JWT)是目前最流行的跨域身份驗證解決方案。為了網路應用環境間傳遞聲明而執行的一種基於JSON的開發標準(RFC 7519),

該token被設計為緊湊且安全的,特別適用於分散式站點的單點登陸(SSO)場景。JWT的聲明一般被用來在身份提供者和服務提供者間傳遞被認證的用戶身份資訊,

以便於從資源伺服器獲取資源,該token也可直接被用於認證,也可被加密。

一、JWT的組成

  下面是JWT的一段示例,分為三個部分,分別是頭部(header),載荷(payload)}和簽證(signature),他們之間用點隔開。

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.
eyJpc3MiOiLmtYHmnIjml6Dlj4wiLCJleHAiOjE1NzExMDIxNTMsInN1YiI6InRlc3RKV1QiLCJhdWQiOiJVU0VSIiwiaWF0IjoiMjAxOS8xMC8xNSA5OjE1OjQzIiwiZGF0YSI6eyJuYW1lIjoiMTExIiwiYWdlIjoxMSwiYWRkcmVzcyI6Imh1YmVpIn19.
25IbZpAbSXBQsr2k3h0IzKRAC6z3OJTWg38VDtcEER8

 二、和傳統session的對比

JWT是基於json的鑒權機制,而且是無狀態的,伺服器端是沒有如傳統那樣保存客戶端的登錄資訊的,這就為分散式開發提供了便利,

因為傳統的方式是在服務端保存session資訊,session是保存在記憶體中的,當客戶量變大的時候,對伺服器的壓力自然會增大,

最關鍵的是在集群分散式中,每一次登錄的伺服器可能不一樣,那麼可能導致session保存在其中一個伺服器,而另外一個伺服器被請求的

時候還是無狀態,除非你再次登錄,這就造成了很大的麻煩,也有人說把session存放在專門的伺服器,每次都去那個伺服器請求,

我不認為這是很好的解決方案,本來集群就是為了高可用,如果你配置session的伺服器壞了,大家都跟著完蛋,所以JWT這種無狀態的方式

就非常適合這種分散式的系統。

 三、程式碼 JwtHelper

光說不練假把式,下面還是來一段程式碼。

還是老方式,先用NuGet把JWT引用進來,這裡需要引入JWT和newtonsoft.json

如下圖所示:

 

然後就是生成JWT的方法。


  static IJwtAlgorithm algorithm = new HMACSHA256Algorithm();//HMACSHA256加密
  static IJsonSerializer serializer = new JsonNetSerializer();//序列化和反序列
  static IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();//Base64編解碼
  static IDateTimeProvider provider = new UtcDateTimeProvider();//UTC時間獲取

const string secret = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC4aKpVo2OHXPwb1R7duLgg";//服務端
public
static string CreateJWT(Dictionary<string, object> payload) { IJwtEncoder encoder = new JwtEncoder(algorithm, serializer, urlEncoder); return encoder.Encode(payload, secret); }

看到這段程式碼,你說覺得怎麼這麼簡單,沒錯,就是這麼簡單,這個方法是被我們引用進來的這個JWT給封裝了,所以看起來很簡單

 

 當時我看到這裡也是有點吃驚,不過我還是對源碼進行了研究,下面貼出一段源碼給大家看看

如下所示,這段程式碼是比較核心的程式碼,在沒有傳遞header的時候,他幫你默認加了header,

其實下面的這段程式碼很容易看懂,無非就是對header和payload進行base64加密(其實這裡說加密也不是很恰當)。

這裡的header你可以自己傳遞進來。

public string Encode(IDictionary<string, object> extraHeaders, object payload, byte[] key)          {              if (payload is null)                  throw new ArgumentNullException(nameof(payload));                var segments = new List<string>(3);                var header = extraHeaders is null ? new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase) : new Dictionary<string, object>(extraHeaders, StringComparer.OrdinalIgnoreCase);              header.Add("typ", "JWT");              header.Add("alg", _algorithm.Name);                var headerBytes = GetBytes(_jsonSerializer.Serialize(header));              var payloadBytes = GetBytes(_jsonSerializer.Serialize(payload));                segments.Add(_urlEncoder.Encode(headerBytes));              segments.Add(_urlEncoder.Encode(payloadBytes));                var stringToSign = String.Join(".", segments.ToArray());              var bytesToSign = GetBytes(stringToSign);                var signature = _algorithm.Sign(key, bytesToSign);              segments.Add(_urlEncoder.Encode(signature));                return String.Join(".", segments.ToArray());          }

 下面一段就是對JWT進行驗證的程式碼,這裡的寫法都差不多,反正都是調用JWT裡面的方法,我們傳遞參數即可。

public static bool ValidateJWT(string token, out string payload, out string message)          {              bool isValidted = false;              payload = "";              try              {                  IJwtValidator validator = new JwtValidator(serializer, provider);//用於驗證JWT的類
IJwtDecoder decoder = new JwtDecoder(serializer, validator, urlEncoder);//用於解析JWT的類
                payload = decoder.Decode(token, secret, verify: true);
isValidted = true;
message
= "驗證成功"; } catch (TokenExpiredException)//當前時間大於負載過期時間(負荷中的exp),會引發Token過期異常 { message = "過期了!"; } catch (SignatureVerificationException)//如果簽名不匹配,引發籤名驗證異常 { message = "簽名錯誤!"; } return isValidted; }

四、調用

class Program      {          static void Main(string[] args)          {              //載荷(payload)              var payload = new Dictionary<string, object>              {                  { "iss","流月無雙"},//發行人                  { "exp", DateTimeOffset.UtcNow.AddSeconds(10).ToUnixTimeSeconds() },//到期時間                  { "sub", "testJWT" }, //主題                  { "aud", "USER" }, //用戶                  { "iat", DateTime.Now.ToString() }, //發布時間                   { "data" ,new { name="111",age=11,address="hubei"} }              };              //生成JWT              Console.WriteLine("******************生成JWT*******************");              string  JWTString = JwtHelper.CreateJWT(payload);              Console.WriteLine(JWTString);              Console.WriteLine();                //校驗JWT              Console.WriteLine("*******************校驗JWT,獲得載荷***************");              string ResultMessage;//需要解析的消息              string Payload;//獲取負載              if (JwtHelper.ValidateJWT(JWTString, out Payload, out ResultMessage))              {                  Console.WriteLine(Payload);              }              Console.WriteLine(ResultMessage);//驗證結果說明              Console.WriteLine("*******************END*************************");          }      }

結果如圖:

 

五、總結

 1、因為json是通用的,所以jwt可以在絕大部分平台可以通用,如java,python,php,.net等

 2、基於jwt是無狀態的,jwt可以用於分散式等現在比較流行的一些框架中。

 3、jwt本身不是加密的,所以安全性不是很高,別人知道了你的token就可以解析了,

  當然你自己也可以對jwt進行加密,設置的過期時間不宜過長,同時不要保存一些重要的資訊,如密碼。

 4、盡量使用https,這也是為了安全。

 5、JWT位元組佔用很少,非常的輕便,所以便於傳輸。

 6、JWT一般放在http的頭部Header中傳輸。

 

—如有錯誤歡迎指出,大家相互進步。