Token 認證(Asp.Net)
場景:前後端分離,在客戶端快取使用者的登入資訊,減少與後端介面的互動次數,同時解決身份認證。
Token
token 產生,使用者登入成功,伺服器端產生一個token並返給前端,前端將token儲存在cookie或者localStorage裡面,然後每次請求時都帶上這個token,一般都帶在請求頭header裡面
token 內容,會話的標識比如UserID,Name,pwd加密,過期時間,簽發時間,頒發人。
token演算法,base64(Header) + . + base64(payload) + . + sign
token 校驗,當伺服器端收到請求時,首先會校驗token,校驗有兩種不同的方式
一, token產生後儲存在伺服器端(redis或者其他比較速度快的快取中) 。優點:可控性強,可以用這個來做單點登入,比如另一個地方登入,就remove掉之前的token。缺點:實現麻煩一點,而且要佔伺服器壓力
二,token產生後伺服器端不儲存,只負責校驗。 優點:大大降低了伺服器的壓力,實現起來,也要相對簡單一點。缺點:token一旦頒發,伺服器端就不可控了,只能等它過期。(用的多)
token jwt演算法 全名 Json Web Tokens
組成:
- Header(頭部,一般包含了token的簽名方式)
- Payload(負載,也就是具體的有效部分)
- Signature(簽名,將前兩部分進行簽名演算法,防止客戶端篡改)
實現方式,將 header 部分和 payload 部分分別進行 base64 演算法,然後用點號“.”隔開拼接,然後進行簽名演算法(MD5),然後在將三部分拼接(點號隔開)就得到了jwt
注意 ,jwt預設是採用base64編碼的,也就是說 客戶端也能解碼得出具體內容的,所以除非特殊情況,重要敏感欄位一定不能放在token中
示例程式碼如下
using System; using System.Collections.Generic; using System.Linq; using System.Security.Cryptography; using System.Text;View Codeusing System.Web; namespace NewWeb.Controllers { public class JWT { public static string SALT = "OXpcRP8jmCfMKumY"; public static string Create(Dictionary<string, object> ExraPayload) { var Header = new Dictionary<string, object>(); Header.Add("tp", "MD5"); var Payload = new Dictionary<string, object>(); // JWT 規定的7個官方屬性,還可附加 Payload.Add("iss", "signBy"); //頒發人 Payload.Add("jti", Guid.NewGuid().ToString()); //jwt的id Payload.Add("exp", System.DateTime.Now.AddMinutes(20));//過期時間 Payload.Add("nbf", System.DateTime.Now);//生效時間 Payload.Add("iat", System.DateTime.Now);//簽發時間 Payload.Add("sub", "subject");//主題 Payload.Add("aud", "audience");//受眾 foreach (var item in ExraPayload) { if (Payload.ContainsKey(item.Key)) { Console.WriteLine($"{item.Key}鍵值已被佔用 不能使用 "); throw new Exception($"{item.Key}鍵值已被佔用 不能使用 "); } else { Payload.Add(item.Key, item.Value); } } // 將Header,Payload 通過Base64加密 通過. 拼接在一塊 string base64Header = Base64Url(Newtonsoft.Json.JsonConvert.SerializeObject(Header)); string base64Payload = Base64Url(Newtonsoft.Json.JsonConvert.SerializeObject(Payload)); string tmp = base64Header + "." + base64Payload; string sign = Md5(tmp + SALT); return base64Header + "." + base64Payload + "." + sign; } //校驗是否合法,是否過期 public static bool Check(string token) { string base64Header = token.Split('.')[0]; string base64Payload = token.Split('.')[1]; string sign = token.Split('.')[2]; string tmp = base64Header + "." + base64Payload; var signCheck = Md5(base64Header + "." + base64Payload + SALT); if (signCheck != sign) { return false; } var dic = Newtonsoft.Json.JsonConvert.DeserializeObject<Dictionary<string, object>>(Base64UrlDecode(base64Payload)); if (Convert.ToDateTime(dic["exp"]) < System.DateTime.Now) { //過期了 return false; } return true; } //校驗是否合法,是否過期 public static Dictionary<string, object> GetPayLoad(string token) { string base64Payload = token.Split('.')[1]; var dic = Newtonsoft.Json.JsonConvert.DeserializeObject<Dictionary<string, object>>(Base64UrlDecode(base64Payload)); return dic; } // Base64加密 public static string Base64Url(string input) { //JWT 作為一個令牌(token),有些場合可能會放到 URL(比如 api.example.com/?token=xxx)。 //Base64 有三個字元+、/和=,在 URL 裡面有特殊含義,所以要被替換掉:=被省略、+替換成-,/替換成_ 。 string output = ""; byte[] bytes = Encoding.UTF8.GetBytes(input); try { output = Convert.ToBase64String(bytes).Replace('+', '-').Replace('/', '_').TrimEnd('='); } catch (Exception e) { throw e; } return output; } // Base64 解密 public static string Base64UrlDecode(string input) { string output = ""; input = input.Replace('-', '+').Replace('_', '/'); switch (input.Length % 4) { case 2: input += "=="; break; case 3: input += "="; break; } byte[] bytes = Convert.FromBase64String(input); try { output = Encoding.UTF8.GetString(bytes); } catch { output = input; } return output; } //MD5 加密 public static string Md5(string input, int bit = 16) { MD5CryptoServiceProvider md5Hasher = new MD5CryptoServiceProvider(); byte[] hashedDataBytes; hashedDataBytes = md5Hasher.ComputeHash(Encoding.GetEncoding("gb2312").GetBytes(input)); StringBuilder tmp = new StringBuilder(); foreach (byte i in hashedDataBytes) { tmp.Append(i.ToString("x2")); } if (bit == 16) return tmp.ToString().Substring(8, 16); else if (bit == 32) return tmp.ToString();//預設情況 else return string.Empty; } } }
用postMan 呼叫介面進行傳參除錯
public ActionResult Login(string name, string pwd) { // 檢查是否存在當前使用者 //進行將資訊傳入字典建立token var dic =new Dictionary<string, object>(); dic.Add(name,pwd); string token = JWT.Create(dic); //驗證token是否正確是否過期 var isChecked = JWT.Check(token); return Content("OK"); }
參考資料:
Aspnet Mvc 前後端分離專案手記(二)關於token認證---https://www.cnblogs.com/jimsfriend/p/10548321.html
JSON Web Token 入門教程 ---http://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html