C# 如何實現Token

什麼是JWT

JWT:Json web token (JWT), 是為瞭在網絡應用環境間傳遞聲明而執行的一種基於JSON的開放標準((RFC 7519).該token被設計為緊湊且安全的,特別適用於分佈式站點的單點登錄(SSO)場景。JWT的聲明一般被用來在身份提供者和服務提供者間傳遞被認證的用戶身份信息,以便於從資源服務器獲取資源,也可以增加一些額外的其它業務邏輯所必須的聲明信息,該token也可直接被用於認證,也可被加密。

傳統的session認證

我們知道,http協議本身是一種無狀態的協議,而這就意味著如果用戶向我們的應用提供瞭用戶名和密碼來進行用戶認證,那麼下一次請求時,用戶還要再一次進行用戶認證才行,因為根據http協議,我們並不能知道是哪個用戶發出的請求,所以為瞭讓我們的應用能識別是哪個用戶發出的請求,我們隻能在服務器存儲一份用戶登錄的信息,這份登錄信息會在響應時傳遞給瀏覽器,告訴其保存為cookie,以便下次請求時發送給我們的應用,這樣我們的應用就能識別請求來自哪個用戶瞭,這就是傳統的基於session認證。

但是這種基於session的認證使應用本身很難得到擴展,隨著不同客戶端用戶的增加,獨立的服務器已無法承載更多的用戶,而這時候基於session認證應用的問題就會暴露出來.

基於session認證所顯露的問題

Session: 每個用戶經過我們的應用認證之後,我們的應用都要在服務端做一次記錄,以方便用戶下次請求的鑒別,通常而言session都是保存在內存中,而隨著認證用戶的增多,服務端的開銷會明顯增大。

擴展性: 用戶認證之後,服務端做認證記錄,如果認證的記錄被保存在內存中的話,這意味著用戶下次請求還必須要請求在這臺服務器上,這樣才能拿到授權的資源,這樣在分佈式的應用上,相應的限制瞭負載均衡器的能力。這也意味著限制瞭應用的擴展能力。

CSRF: 因為是基於cookie來進行用戶識別的, cookie如果被截獲,用戶就會很容易受到跨站請求偽造的攻擊。

基於token的鑒權機制

基於token的鑒權機制類似於http協議也是無狀態的,它不需要在服務端去保留用戶的認證信息或者會話信息。這就意味著基於token認證機制的應用不需要去考慮用戶在哪一臺服務器登錄瞭,這就為應用的擴展提供瞭便利。

流程上是這樣的:

  • 用戶使用用戶名密碼來請求服務器
  • 服務器進行驗證用戶的信息
  • 服務器通過驗證發送給用戶一個token
  • 客戶端存儲token,並在每次請求時附送上這個token值
  • 服務端驗證token值,並返回數據

這個token必須要在每次請求時傳遞給服務端,它應該保存在請求頭裡, 另外,服務端要支持CORS(跨來源資源共享)策略,一般我們在服務端這麼做就可以瞭Access-Control-Allow-Origin: *。

那麼我們現在回到JWT的主題上。

JWT的構成

第一部分我們稱它為頭部(header),第二部分我們稱其為載荷(payload, 類似於飛機上承載的物品),第三部分是簽證(signature).

C# MVC實現token

1.在NuGet中引用JWT

2.創建一個實體 UserInfo類

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace WebApplication1.model
{
 public class UserInfo
 {
  public string UserName { get; set; }

  public string Pwd { get; set; }
 }
}

3.創建JWT幫助類

using JWT;
using JWT.Algorithms;
using JWT.Serializers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace WebApplication1.model
{
 public class JwtHelp
 {
  //私鑰 web.config中配置
  //"GQDstcKsx0NHjPOuXOYg5MbeJ1XT0uFiwDVvVBrk";
  private static string secret = "GQDstcKsx0NHjPOuXOYg5MbeJ1XT0uFiwDVvVBrk";
  //ConfigurationManager.AppSettings["Secret"].ToString();

  /// <summary>
  /// 生成JwtToken
  /// </summary>
  /// <param name="payload">不敏感的用戶數據</param>
  /// <returns></returns>
  public static string SetJwtEncode(Dictionary<string, object> payload)
  {

   //格式如下
   //var payload = new Dictionary<string, object>
   //{
   // { "username","admin" },
   // { "pwd", "claim2-value" }
   //};

   IJwtAlgorithm algorithm = new HMACSHA256Algorithm();
   IJsonSerializer serializer = new JsonNetSerializer();
   IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();
   IJwtEncoder encoder = new JwtEncoder(algorithm, serializer, urlEncoder);

   var token = encoder.Encode(payload, secret);
   return token;
  }

  /// <summary>
  /// 根據jwtToken 獲取實體
  /// </summary>
  /// <param name="token">jwtToken</param>
  /// <returns></returns>
  public static UserInfo GetJwtDecode(string token)
  {
   IJsonSerializer serializer = new JsonNetSerializer();
   IDateTimeProvider provider = new UtcDateTimeProvider();
   IJwtValidator validator = new JwtValidator(serializer, provider);
   IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();
   var algorithm = new HMACSHA256Algorithm();
   IJwtDecoder decoder = new JwtDecoder(serializer, validator, urlEncoder, algorithm);
   var userInfo = decoder.DecodeToObject<UserInfo>(token, secret, verify: true);//token為之前生成的字符串
   return userInfo;
  }
 }
}

4.創建一個編碼類DESCryption

using System;
using System.Text;
using System.Security.Cryptography;
using System.IO;
using System.Configuration;

namespace JWT.MvcDemo.Help
{

 public class DESCryption
 {

  /// <summary>
  /// //註意瞭,是8個字符,64位
  /// </summary>
  private static string PrivateRsa = ConfigurationManager.AppSettings["PrivateRsa"];

  /// <summary>
  /// //註意瞭,是8個字符,64位
  /// </summary>
  private static string PublicRsa = ConfigurationManager.AppSettings["PublicRsa"];

  /// <summary>
  /// 加密
  /// </summary>
  /// <param name="data"></param>
  /// <returns></returns>
  public static string Encode(string data)
  {
   byte[] byKey = Encoding.ASCII.GetBytes(PrivateRsa);
   byte[] byIV = Encoding.ASCII.GetBytes(PublicRsa);

   DESCryptoServiceProvider cryptoProvider = new DESCryptoServiceProvider();
   int i = cryptoProvider.KeySize;
   MemoryStream ms = new MemoryStream();
   CryptoStream cst = new CryptoStream(ms, cryptoProvider.CreateEncryptor(byKey, byIV), CryptoStreamMode.Write);

   StreamWriter sw = new StreamWriter(cst);
   sw.Write(data);
   sw.Flush();
   cst.FlushFinalBlock();
   sw.Flush();
   return Convert.ToBase64String(ms.GetBuffer(), 0, (int)ms.Length);

  }

  /// <summary>
  /// 解密
  /// </summary>
  /// <param name="data"></param>
  /// <returns></returns>
  public static string Decode(string data)
  {
   byte[] byKey = Encoding.ASCII.GetBytes(PrivateRsa);
   byte[] byIV = Encoding.ASCII.GetBytes(PublicRsa);

   byte[] byEnc;
   try
   {
    byEnc = Convert.FromBase64String(data);
   }
   catch
   {
    return null;
   }

   DESCryptoServiceProvider cryptoProvider = new DESCryptoServiceProvider();
   MemoryStream ms = new MemoryStream(byEnc);
   CryptoStream cst = new CryptoStream(ms, cryptoProvider.CreateDecryptor(byKey, byIV), CryptoStreamMode.Read);
   StreamReader sr = new StreamReader(cst);
   return sr.ReadToEnd();
  }

 }
}

5.創建一個返回消息類

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace JWT.MvcDemo.Models
{
 public class DataResult
 {
  /// <summary>
  /// 
  /// </summary>
  public string Token { get; set; }

  public bool Success { get; set; }

  public string Message { get; set; }

 }
}

6.創建一個控制器用於生產token

using JWT.MvcDemo.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using WebApplication1.model;

namespace WebApplication1.Controllers
{
 public class JwtController : Controller
 {
  // GET: Jwt
  public ActionResult Index()
  {
   return View();
  }

  /// <summary>
  /// 創建jwtToken
  /// </summary>
  /// <param name="username"></param>
  /// <param name="pwd"></param>
  /// <returns></returns>
  public ActionResult CreateToken(string username, string pwd)
  {

   DataResult result = new DataResult();

   //假設用戶名為"admin",密碼為"123" 
   if (username == "admin" && pwd == "123")
   {

    var payload = new Dictionary<string, object>
    {
     { "username",username },
     { "pwd", pwd }
    };

    result.Token = JwtHelp.SetJwtEncode(payload);
    result.Success = true;
    result.Message = "成功";
   }
   else
   {
    result.Token = "";
    result.Success = false;
    result.Message = "生成token失敗";
   }

   //return Json(result);
   //get請求需要修改成這樣
   return Json(result,JsonRequestBehavior.AllowGet);
  }
 }
}

7.創建一個自定義過濾器

using JWT.MvcDemo.Help;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Security;
using WebApplication1.model;

namespace JWT.MvcDemo.App_Start
{
 public class MyAuthorizeAttribute : AuthorizeAttribute
 {

  private readonly string TimeStamp = ConfigurationManager.AppSettings["TimeStamp"];

  /// <summary>
  /// 驗證入口
  /// </summary>
  /// <param name="filterContext"></param>
  public override void OnAuthorization(AuthorizationContext filterContext)
  {
   base.OnAuthorization(filterContext);
  }

  /// <summary>
  /// 驗證核心代碼
  /// </summary>
  /// <param name="httpContext">fbc8ZBLd5ZbtCogcY9NUVV4HZbPln1lb</param>
  /// <returns></returns>
  protected override bool AuthorizeCore(HttpContextBase httpContext)
  {

   //前端請求api時會將token存放在名為"auth"的請求頭中
   var authHeader = httpContext.Request.Headers["auth"];
   if (authHeader == null)
    return false;

   //請求參數
   string requestTime = httpContext.Request["rtime"]; //請求時間經過DESC簽名
   if (string.IsNullOrEmpty(requestTime))
    return false;

   //模擬生成rtime 時間戳,即登錄的時間,加密.       //實際生產中這段代碼應該在請求段。此處隻為瞭程序驗證通過
   string r= DESCryption.Encode(DateTime.Now.ToString());
   requestTime = r;


    //請求時間RSA解密後加上時間戳的時間即該請求的有效時間
    DateTime Requestdt = DateTime.Parse(DESCryption.Decode(requestTime)).AddMinutes(int.Parse(TimeStamp));
    DateTime Newdt = DateTime.Now; //服務器接收請求的當前時間

   if (Requestdt < Newdt)
   {
    return false;
   }
   else
   {
    if (authHeader != null)
    {
     //進行其他操作
     var userinfo = JwtHelp.GetJwtDecode(authHeader);
     //舉個例子 生成jwtToken 存入redis中 
     //這個地方用jwtToken當作key 獲取實體val 然後看看jwtToken根據redis是否一樣
     if (userinfo.UserName == "admin" && userinfo.Pwd == "123")
      return true;
    }

   }

   return false;
  }

  /// <summary>
  /// 驗證失敗處理
  /// </summary>
  /// <param name="filterContext"></param>
  protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
  {
   base.HandleUnauthorizedRequest(filterContext);
   filterContext.Result = new RedirectResult("/Error");
   filterContext.HttpContext.Response.Redirect("/Home/Error");
  }


 }
}

8.在要需要過濾的控制器方法上添加標簽,標簽就是自定義過濾器名稱。

using JWT.MvcDemo.App_Start;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace WebApplication1.Controllers
{
 public class HomeController : Controller
 {
  public ActionResult Index()
  {
   return View();
  }

  [HttpPost]
  [MyAuthorize]
  public string About()
  {
   string rtJson = "{\"code\": 0}";
   try
   {

    rtJson = "{\"code\":0,\"data\":[],\"msg\":\"Your application description page.\",\"count\":1}";
   }
   catch
   {
    rtJson = "{\"code\": 0}";
   }
   return rtJson;
  }


  public ActionResult Contact()
  {
   ViewBag.Message = "Your contact page.";

   return View();
  }
 }
}

9.測試獲取token

10.客戶端將token放入header中達到攜帶token目的。

11.需要在web.config 中添加設置值

 <add key="Secret" value="GQDstcKsx0NHjPOuXOYg5MbeJ1XT0uFiwDVvVBrk"/>
  <add key="PrivateRsa" value="GQDstcKs"/>
  <add key="PublicRsa" value="DVvVBrkx0"/>
  <add key="TimeStamp" value="2"/>

以上就是C# 如何實現Token的詳細內容,更多關於C# 實現Token的資料請關註WalkonNet其它相關文章!

推薦閱讀: