Aspnet Mvc 先後端分離項目手記(二)關於token認證

在先後端分離的項目中,首先咱們要解決的問題就是身份認證html

以往的時候,咱們使用cookie+session,或者只用cookie來保持會話。前端

 

一,先來複習一下cookie和sessionweb

首先咱們來複習一下在aspnet中cookie和session的關係,作一個簡單試驗redis

這是一個普通的view沒有任何處理算法

 

 

能夠看到,沒有任何東西(cookie),而後當咱們寫入一個session以後json

\後端

 

 

 

 會發現多了一個名爲ASP.NET_SessionId的cookie。咱們都知道在aspnet中,session是保存在服務器端的內存中的,而http協議是無狀態的,那麼他是怎麼肯定不一樣請求的sessionapi

沒錯,session是藉助cookie來實現的:cookie中保存着 session的key,當咱們清除掉瀏覽器緩存時,會發現session也找不到了,就是這個緣由。跨域

使用session來保持會話有幾個很嚴重的缺點:1 session容易丟失;2沒法支持分佈式;3,cookie 對跨域的支持很差瀏覽器

 

因此就用到了咱們今天說的token

二,token 

1,token的產生

通常是用戶登陸成功,服務器端產生一個token並返給前端,前端將token保存在cookie或者localStorage裏面,而後每次請求時都帶上這個token,通常都帶在請求頭裏面

 2,token的內容

通常的token裏面必須有的是:1,會話用戶的標識:好比userid。2,token的過時時間,若是想更完整一點,能夠加上token的頒發者,簽名等等

3,token的生成算法,通常是由服務器端將token的主要內容,過時時間等等作非對稱加密,而後進行簽名算法(防止客戶端更改),具體看後面jwt

 

4,token校驗

當服務器端收到請求時,首先會校驗token,校驗有兩種不一樣的方式

 一, token產生後保存在服務器端(redis或者其餘比較速度快的緩存中) 。優勢:可控性強,能夠用這個來作單點登陸,好比另外一個地方登陸,就remove掉以前的token。缺點:實現麻煩一點,並且要佔服務器壓力

二, token產生後服務器端不保存,只負責校驗。 優勢:大大下降了服務器的壓力,實現起來,也要相對簡單一點。缺點:token一旦頒發,服務器端就不可控了,只能等它過時。

    具體用哪一種看具體的需求。若是不是作可控性要求很強,我的建議第二種。

 

5 jwt 

jwt 全名Json Web Tokens,算是一種token的規範吧

園子裏面有很不不錯的介紹 ,好比這篇:阮一峯 jwt介紹   http://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html

 

組成有三部分

  • Header(頭部,通常包含了token的簽名方式)
  • Payload(負載,也就是具體的有效部分)
  • Signature(簽名,將前兩部分進行簽名算法,防止客戶端篡改)

實現方式,將header部分和payload部分分別進行base64算法,而後用點號「.」隔開拼接,而後進行簽名算法,而後在將三部分拼接(點號隔開)就獲得了jwt

注意 ,jwt默認是採用base64編碼的,也就是說 客戶端也能解碼得出具體內容的,因此除非特殊狀況,重要敏感字段必定不能放在token中

 

 如下是具體實現

using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Web;

namespace Rk.JWT
{
 
    public class Jwt
    {

        //參考自 阮一峯 jwt介紹  http://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html
 


        public static string SALT = "OXpcRP8jmCfMKumY";
     
         

        /// <summary>
        /// 
        /// </summary>
        /// <param name="ExraPayload">額外的信息</param>
        /// <returns></returns>
        public static string Create(Dictionary<string,object> ExraPayload)
        {
            var Header = new Dictionary<string, string>();
            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))
                {

                    throw new Exception($"{item.Key}鍵值已被佔用 不能使用 ");
                }
                else
                {
                    Payload.Add(item.Key, item.Value);
                }
            }
            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;

        }


        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;
        }
        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;
        }
        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;
           
        }
    }
    
}

  

  使用方式

 

 public class HomeController : BaseController
    {


        public ActionResult Login(string username, string pwd)
        {

            /// 1, todo 驗證用戶名密碼正確


            //2,//在token中加入用戶id,建立token
            var dic = new Dictionary<string, object>();
            dic.Add("userid", "20125521225858");
            string token = JWT.Jwt.Create(dic);


            //驗證token是否正確是否過時
            var isChecked = JWT.Jwt.Check(token);

            return Content("");

        }
    }

  

 下一篇咱們將會聊一聊 rest 風格url在先後端分離項目中的使用

相關文章
相關標籤/搜索